程序员如何实现编码黄金标准?
编程的黄金标准是Linux,版本控制的黄金标准是Git——那么程序员是否可以合理地同时使用代码和模型,并用版本控制管理两者?本文分享如何利用Linux,Git,MetaEdit+三剑客解决版本管理、建模和编程的问题。
以下为译文:
有人经常讽刺码农不愿意建模,不过我持保留意见。
我认为我们只是不愿意浪费时间去做那些无法产生任何实际的效益,只为了满足某些毫无意义的规则的事情。而且我们中的绝大多数人都不愿意做两次同样的事情。所以为了管理而事后画UML、做文档的事情不会有人做的。
我们喜欢我们的语言、框架和工具,对我们来说放弃这些很困难。非程序员也许很喜欢建模,他们喜欢可见的形式,但通常建模并不能代替代码。
说服程序员进行建模最常见的说辞就是状态机。if...elseif或switch...case的结构并不清晰,文本形式的DSL也不行,只有图能做到。就连Linux的内核GitHub上都有个状态机的图——尽管是用ASCII画的!
* | * V * +---> STARTUP ----+ * | | | * | V | * | DRAIN ----+ * | | | * | V | * +---> PROBE_BW ----+ * | ^ | | * | | | | * | +----+ | * | | * +---- PROBE_RTT <--+
大多数人都因为ASCII的使用不便和耗费时间,而宁愿选择实际的图形工具。而先画图再写代码或者先写代码再画图会导致重复性工作和两者不同步,因此应该以状态模型作为唯一的标准——如同用其他语言编写项目的某些部分,一切只为了选择最佳表现形式。
只要一切都能自动构建和集成,并且版本控制也能正常使用,那就很好。但是,一般情况下事实并非如此:因此才有了上述ASCII画的图形。
那么我们可以改变这种状况吗?我们是否可以合理地同时使用代码和模型,并用版本控制管理两者?能否不需要大量的额外工作,特别是在使用的时候不需要额外工作? 好了,废话你也看够了,下面开始上干货。
1.三位芬兰剑客:Linux,Git 和 MetaEdit+
由于编程的黄金标准是Linux,版本控制的黄金标准是Git,而两者都源自芬兰,让我们来看看怎样让第三个芬兰朋友与两者融合提供帮助。
MetaEdit+是一个建模工具,也是一个语言工作台(一种可以让你轻松地创建自己的建模语言和代码生成的工具)。很方便的一点是,它还支持Linux、Git以及IDE等其他集成工具。而且它还有一个很好的“数字手表”的状态机示例及其完整的代码生成功能。
完整的代码生成功能?!那岂不成了一个可怕的流程图,上面充斥着IF,GOTO和IDE不支持的内联代码片段?又或者非常笨重,难以阅读,效率低下的一堆代码吗?幸运的是不会。这种图形语言和它的代码生成器都是领域专用的,也就是说是针对任务微调过的(例如制作数字手表应用的任务)。因此,里面的东西大家都认识,如果你没有接触过该领域的工作,那么这一切也许对你来说很陌生,但是你依然可以理解模型和代码。
上述是一个秒表应用程序的图。这个手表可以处于多种状态,你可以按按钮进行操作并移至新状态,操作类似于设置时间变量或打开显示屏上的图标等。简而言之,该应用的内部处理由代码生成和一个小状态机框架组成,你可以通过该语言从手表终端用户的角度进行设计。
你可以试试看Linux下的MetaEdit,如果你的IDE使用Eclipse的话,还可以下载Eclipse插件。有关详细信息,请参照该手表的示例:
http://www.metacase.com/support/55/manuals/watchtut/we-Preface_.html。
2.模型到代码
那么让我们来看看代码吧。
从Eclipse中的Graph Browser窗格中选择WatchModels图,然后从弹出的菜单中选择编辑Graph的Properties ...,并将Generation目标平台设置为'Java:Linux'。再次选择并打开弹出菜单,然后选择“Run Autobuild”。(
如果你不在Eclipse上,那么请在MetaEdit +窗口的Graph Browser选项卡中选择WatchModels,从弹出的菜单中选择Properties...,然后选择Generate... | Autobuild。
如此,代码就生成好了(如果使用了插件,则添加到Eclipse项目中;如果没有使用的话,则在Generated Files窗口中打开),还进行了编译和运行。
我们可以选择Sporty WatchModel,然后按Mode启动秒表。正如我们从模型中看到的那样,按Up键时秒表开始运行,再次按Up键时秒表停止运行。按Down键时重置。
3.模型到Git
好,现在我们有模型,有生成的代码,但是版本控制和协作呢?MetaEdit+有一个多用户版本,可以自动保持每个人同步,无需合并,也不会在有人编辑的时候锁定让别人无法访问。这虽然很好,这克服了合并模型改动的噩梦,但仍然没有版本控制。为此,我们可以使用常用的Git或SVN。假设你想建一个本地的Git代码仓库,同时还推送到GitHub或BitBucket。
首先,将手表演示代码仓库(https://github.com/MetaCase/watchdemo)复制到你自己的GitHub上(或BitBucket)。在~/metaedit目录中,执行mkdir git。将该新目录和你的Git帐号URL(不带/ watchdemo后缀)添加到~/metaedit/.vcsPaths文件中,如下所示:
gitBaseDir=/home/myuser/metaedit/git gitBaseURL=https://github.com/myuser
退出MetaEdit+,并确保你已安装好了xterm,因为Git和SVN集成会用到它。然后运行以下命令,在本地克隆你的在线代码仓库:
metaedit textForMERL: "_vcsInitClone('watchdemo')" logoutAndExit
xterm会提示你输入GitHub或BitBucket的密码,如果一切顺利,你可以在一切结束后关闭xterm。下面使用Eclipse Graph Browser工具栏按钮,刷新并更改设置指定watchdemo作为代码仓库名。再通过Graph Browser按钮重新启动MetaEdit+。如果想看原来的内容都发生了哪些变动,那么请参阅博文《扩展建模语言》(http://www.metacase.com/blogs/stevek/blogView?showComments=true&entry=3714901506#Extending)。
4.代码实现的自由
在图形浏览器中双击打开秒表的图形界面(必要时刷新,然后点击WatchModels-> TST->Stopwatch)。点击上面的Down按钮可以显示该模型,我们可以看到这个界面有点难看:如果想将startTime重置为零,那么需要点击stopTime - stopTime。“官方”的手表演示教程以此为基础,通过添加常量来改进语言模型,但是在这里我们想利用它来演示如何集成一些“真实”的代码。我们利用模型来指定一个可以调用的函数,然后代码中实现我们想要的功能。
选择并删除startTime,startTime和stopTime连接到Down按钮的操作。单击工具栏上的“Function”,然后单击图表添加名为“reset”的功能,然后单击工具栏末端的“Run relationship”的!图标,并从Action连接到”reset”(对话框只需点确定即可,因为此处只有一个操作,我们不需要设置顺序)。
5.集成手写的代码
现在我们可以为我们的模型生成代码,构建并运行它。从Graph Browser和Autobuild中选择WatchModels。选择Sporty,按Mode进入秒表应用程序,然后按几次Up启动和停止秒表。然后,你将返回到Stopped的状态,看到你测量的时间。 现在,在Stopped状态下按Down将不会改变显示的时间:它会调用reset方法,但是该函数还没有内容,所以什么也没做。
在生成的Stopwatch.java文件中,编辑reset方法,在MD5注释之间添加一行,将stopTime设置为零,然后保存:
setStopTime(new METime());
注意,我们正在编辑生成的文件。通常生成的文件是不应该被编辑的,但是这里MD5保护区域将会处理好一切,维护我们手动写的代码,并根据模型重新生成其余部分。
关闭正在运行的Sporty和WatchModels。使用Autobuild重建WatchModels,然后再次尝试Sporty,依次按下Mode,Up,Up,Down,然后看看Down现在确实将秒表时间重置成零了。
6.在Git中结合模型和代码
现在Git中有我们的模型,生成的标准代码,以及手动添加并自动维护的自定义代码。最后我们还需要扩展Git集成,在管理模型的版本时将手动代码包含进来。(如果可能的话,通常最好将手动代码与生成的代码分开,但在这里我们故意挑选一种更难的方法。)
MetaEdit+的Git集成由生成器处理,因此你可以根据需要进行调整。在MetaEdit+主窗口中,选择Repository | Changes & Versions Tool,选择VCS Settings | Paths,并在$dbName和$ dbBaseDir的设置之后加入以下代码:
/* $srcDir is for source code you also want versioned; '' for none */ $srcDir = __(subreport '_default directory' run 'WatchModels' sep 'src')
保存并在列表中进一步选择_vcsCheckIn生成器,然后在_osCd($vcsWorkingDir)的2行或3行之前加入以下代码:
if $srcDir then _osCopy($srcDir sep '.', $vcsWorkingDir sep 'src') ' || ' _osPause() newline endif
我们在Save Version上check in的时候,它会将$srcDir的内容复制到我们的Git目录中。 同样,如果我们想要从Git复制源代码,那么可以check out,然后保存,然后编辑_vcsRestoreDB并在最后一行(pushd)之前加入以下代码:
if $srcDir then _osMkdir($srcDir) ' || ' _osPause() newline _osCopy($vcsWorkingDir sep 'src' sep '.', $srcDir) ' || ' _osPause() newlineendif
正如你所见,我们创建$srcDir是为了安全考虑。那些_osMkdir和_osCopy的调用只是子生成器,可以在当前平台上生成正确的批处理或shell命令,因此我们可以一次性编写这些版本控制的脚本,而无需为每个平台复制该逻辑。 如果该命令出错,那么 ' || ' _osPause()将暂停,以方便我们查看问题所在。
最后,如果我们使用Eclipse插件,那么源代码将不会存储在MetaEdit+常用的位置上(我们在$srcDir中的设置),因此我们可以通过在之前创建的~/metaedit/.vcsPaths文件中加入srcDir的设置来覆盖原本的设置。在src目录的Eclipse Properties中输入绝对路径,如下所示:
srcDir=/home/myuser/eclipse-workspace/WatchModels/src
7.用版本控制管理修改
返回“Changes & Versions”工具,并按“Refresh”(或通过MetaEdit +主窗口的工具栏或“Repository”菜单重新打开)。里面显示了自上一版本以来的变更,即更改了将秒表置零时用到的重置对象。如果想查看这些变更,那么可以查看文档树,或查看Stopwatch的弹出菜单:以图形方式显示,并以文本的方式比较它们。
选择最顶端的Working Version,然后启用“Show All Versions”以查看以前的版本。输入新版本号和版本注释,例如“9”或'Added reset function and srcDir'。试试看Save Version,测试并提交我们对模型和版本控制生成器所做的更改(如果你输入错误的密码或类似的简单错误,那么可以在~/metaedit目录中通过gitCheckIn.bat重新运行) 。
你可以从GitHub或BitBucket中看到代码仓库(versionedDB/)中的变更和新加的源代码文件(src/),我们还将生成器保存成了文本文件(metamodel/Graph/),以便你轻松地查看变化内容。
你可以尝试为模型本身添加一些新功能,例如一个Laptime状态。然后执行Autobuild,并查看reset()中手写的代码被保留了下来。保存版本,通过远程Git中看看是否所有内容的都符合预期。
8.小结
我们采用了一个现成的建模语言及其生成器,然后添加了一些好东西:
- 只需两个设置值,就可以在本地和GitHub或BitBucket上建立起版本控制。
- 扩展Git集成,以对手动编写的代码进行版本控制。在MetaEdit+和Eclipse中自由编辑,然后利用Save Version对所有内容进行版本控制。
值得一提的是:我们在这里所做的所有变更都没有专门针对这些情况进行设置。只要在构建工具时使用的组件能够通过多种方式访问和理解,并且是开源的,那么即使是非常广泛的更改也可以轻松完成。
以下三个方面的工作皆是如此:
- 模型:添加'reset'及手动编写的代码,为建模者提供自由;
- 元模型:为手工编写的函数扩展建模语言及其生成器;
- 工具集成:扩展版本控制,维护生成和手动编辑的源代码文件。
虽然MetaEdit+和Linux与Git一样出自芬兰,但我们也拥有国际主义精神:上述版本控制集成也适用于Windows、SVN等。
原文:http://www.metacase.com/blogs/stevek/blogView?entry=3714903141
作者:Steven Kelly,MetaCase的CTO和DSM论坛的联合创始人。他在领域特定建模方面拥有超过20年的咨询和构建工具的经验。
译者:弯月,责编:郭芮