再转次梁子谦的文章 - 职责分离的编码操作手册

职责分离的编码操作手册

本次codereview发现的问题大多是已经被前几期点评提到过,于是重新读了一下被review的代码,发现了一个平时大家不是很在意的问题:从代码文件的长度上,反映出来关于控制编码过程复杂度的习惯。

这次review的代码有看到class定义就超过300行的案例,这类代码或许大家平时也可以理解,因为很多功能聚集在一起,并且相互依赖,例如大部分游戏都有的player class,功能实在太多,甚至有上千行的class 定义,player.h include了整个项目中50%以上的模块头文件,并被大部分cpp文件include,构成了调用结构上的大热点。这种源码想必大家都可以想象的出来,维护起来非常非常的困难,在脑海中构建一个该模块的分析视图,是多么痛苦。

于是想到趁着这次代码点评的机会,翻出来去年写的一个小品文,跟大家分享一些个人的编程观念和习惯,通过更深入的设计工作,达成相对合理的职责切分粒度,并使用构思型注释+翻译型编码过程,增强开发的准确和可控性。

这里列举一些软工行业bible或流传已久的观点,作为后续讨论的铺垫:
1 我们经常听到工程师说,工作效率提升的瓶颈在于无法专注于编码工作,工作时经常被谈话,会议,rtx等不定时插入的事件打断。
2 《人件》(peopleware)一书中有讲到:进入专注状态,需要15分钟连贯的工作,而一旦被打断退出专注状态,就像堆栈被清空,再次建立思维的缓存和上下文环境,需要另外的15分钟,在理想情况下。
3 在booch的OOA-OOD一书中提到:人脑中管理的事件不可过多,最好少于7件事,这样可以保证思考的质量,过于拥挤的思考目标,会让大脑的思考能力过载,有顾此失彼的风险。
4 我们经常听说一些优秀的geek说,“我最高效的时间在深夜23点到25点,1小时可以抵得上白天8小时,blahblah……”除了作息习惯生物钟方面的原因,也有在夜深人静的时候,干扰打断较少有很大的关系。
5 我们经常听一些老程序员说,笔误是无法避免的,只能靠编辑器的自动功能来降低,是这样吗?

——如上这些情况,作为一个程序员,我们如何来做?
一方面,我们寄希望于团队成员的沟通有效性,彼此尊重对方的工作时间,降低无意义的打断,提升合作伙伴综合素质与办公室礼仪,避免大声喊话,免提接打电话,以及rtx群聊等,但是正如您心中所想,这只是在理论上存在希望。
另一方面,我们从改变我们编码工作的工程性角度,提高我们的抗击打能力避免破防,提高韧性减少暴击。下面我们来看看,如何做可以接近这一目标。

我们把编码的工作分为好几个阶段,每个阶段尽可能专注于一个独立的工作任务、职责。

现在假设我们要完成一个模块(大到服务,小到数据结构,总之就是一个工作单位,称作模块),在正常情况下,我们假设工程师可以正确的完成(物理设计的部分今天我们不讨论了)这个模块的,只是过程的效率和最终产出物的质量有所不同。

三个基本层面包括:功能设计,实现规划,与翻译型coding。

1 模块的职责设计——这个模块在整体系统中的价值,为什么非要写这么一个东西不可,不写可以吗?这一步包含的工作还有,模块的上下游接口与界面行为,例如调用者与被调用是如何合作的?这些逻辑之间的时序关系如何?状态机的某些状态是否有冲突?等等。

2 模块内部的层次设计,例如接口层,逻辑层,核心数据层,对外依赖层,等等。这一步解决的问题还包括模块内部各子模块的可扩展性,各子模块间的逻辑依赖与调用关系依赖关系,是否需要中间层屏蔽扩展或依赖倒置?等等。

3 模块的核心数据结构,核心算法,技术难点如何实现的细节确认——anyway,我们知道在大多数系统中这部分挑战不会让工程师辗转难眠,如果不是这种情况,核心设计还有很大不确定性的话,请把这个步骤放在1号阶段之前,确认该模块的物理可行性。

4 合理粒度的职责切分,即头文件。并在头文件中使用注释明确下来这个子模块的上下游合作者(其他子模块),大家是如何互动起来共同完成工作的,等等。

5 .c(.cpp)文件的函数名和构思型注释。这一步是重点、关键,先写构思型注释再写代码。在注释中用程序员最熟悉的自然语言写入分支,循环,大括号、段落、时序等,在编写注释时,其实我们考虑的健壮性,容错,边界,分支,循环条件,数据类型,资源的分配与释放等等如何让计算机按照我们的意图完成工作的核心价值。这一步决定了计算机将要做什么,如何做,构思型注释可以保证编码者使用最自然最符合自己思维习惯的方式来思考,并表达构思的成果。

6 翻译型编码。将函数实现中的人类语言注释翻译成c/c++/java/c#/python/lua/lisp/.sh等各种编程语言,翻译工作无疑是需要我们一些语法运用的技巧,变量名函数名和外部库函数的信息,现在把它们都转化为编码者的语言,和作品。保守估计,这部分也要占据我们稀有的七件事中的一半多,那么在做翻译工作的同时,如果我们还在做流程或资源管理接口设计健壮性陷阱规避的话,我们的头脑真的是太可怜了,它很可能不堪重负(尤其是做比如大型游戏这样极为复杂逻辑的时候)。避免这种风险,让复杂的事情在构思中完成,让表述这件本身已经很复杂的事情在翻译型编码中完成。

综上,1-4是设计,5是实现规划构思,6是翻译编码,如果其他都不做,但是能坚持把5和6分开,代码质量(我猜:-)也会有明显的提升,让自己的工作简单起来,准确起来!

补充:在hacker的工作方法中,是比较推崇灵感和即兴发挥,在编码的同时完成绝妙的设计,但是与大型项目的工程师不同,hacker考虑的是作品的功能,乐趣,而不用在脑海中始终警戒“不够强壮的一百个理由”“会把上下游模块带进沟里的两百个陷阱”“行行都清晰可理解的三百行代码”,同时hacker的编码时段、地点与正常工程师也稍有不同,不多讨论。
另外推荐读物包括以上提到的peopleware,OOA-OOD,与一个很有意思的小册子:番茄工作法图解。

再转次梁子谦的文章 - 职责分离的编码操作手册

时间: 2024-08-12 21:24:44

再转次梁子谦的文章 - 职责分离的编码操作手册的相关文章

dedeCMS中单独调用子栏目模板和子栏目的文章时修改源代码给channel和chanenartllist加上limit

在网站文件中找到include-taglib-chanel.lib.php,和chaneartllist.lib.php 下载用php的IDE打开, chanel.php加入limit属性修改如下 <?php /** * 获取栏目列表标签 * * @version $Id: channel.lib.php 1 9:29 2010年7月6日Z tianya $ * @package DedeCMS.Taglib * @copyright Copyright (c) 2007 - 2010, Des

黄聪:WordPress 多站点建站教程(三):主站如何调用子站的文章内容、SQL语句如何写?

1.如果懂得编程的朋友可以SQL语句,然后加上PHP函数等操作就可以通过直接调用网站的数据库信息来实现想要达到的目的. 既然要用到SQL语句首先得对WordPress多站点数据库有一个了解,多站点激活后会多出这么几张表wp_site,wp_sitemeta,wp_blogs,wp_blog_versions其中最重要的是wp_blogs这张表,它将你创建的每一个子站点访问地址,以及创建和修改时间等等都存到了这张表里面.还有一点就是当你创建一个子站点后会多出一些中间带有数字的数据表,比如wp_2_

【网页前端】WeX5架构下,WinDialog子窗口1传递参数给主窗口关闭,再弹出子窗口2失败

子窗口1的参数传递和关闭窗口函数         this.owner.send({            name:name.toString(),            value:id        }); this.close(); 子窗口2打开的函数 case window.mainRetName.SelectSchool:                    //justep.Util.hint(event.data.value);                    this.

【iOS开发每日小笔记(九)】在子线程中使用runloop,正确操作NSTimer计时的注意点 三种可选方法

这篇文章是我的[iOS开发每日小笔记]系列中的一片,记录的是今天在开发工作中遇到的,可以用很短的文章或很小的demo演示解释出来的小心得小技巧.它们可能会给用户体验.代码效率得到一些提升,或是之前自己没有接触过的技术,很开心的学到了,放在这里得瑟一下.其实,90%的作用是帮助自己回顾.记忆.复习. 一直想写一篇关于runloop学习有所得的文章,总是没有很好的例子.正巧自己的上线App Store的小游戏<跑酷好基友>(https://itunes.apple.com/us/app/pao-k

关于父元素,子元素,同级元素的DOM操作技巧

复杂,沉重的web应用在现在是常态,想jquery这样的易于使用的库,跨浏览器的兼容性,各种各样的功能,在操作HTML上非常有帮助.所以难怪很多开发者选择使用这样的库,而不是过去有很多问题原生的DOM API.虽然浏览器的差异仍然是一个问题,今天的DOM是比5年或6年前jQuery刚开始流行时更好. 在这篇文章中,我将讨论和展示一些不同的DOM的功能,可以用来操纵HTML,主要聚焦在父元素,子元素和同级节点的关系上. 在最后还是介绍到浏览器的支持兼容情况.但是有一点需要注意的是像jquery这样

在子线程中使用runloop,正确操作NSTimer计时的注意点 三种可选方法

游戏中有一个计时功能.在1.0版本中,使用了简单的在主线程中调用: 1 + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo; 的方法.但是当每0.01秒进行一次repeat操作时,NSTimer是不准的,严重滞后,而改成0.1秒repeat操作,则这种

实现根据父节点显示子节点的无极限分类的完整操作

数据表是设计 包含 id  name   pid   三者 就可以了 第一步: controller: public function actionFunctionPoint() { $items = FunctionPoint::getFunctionPoint(); return $this->render('function-point', [ 'items' => $items, ]); } 第二部 model操作数据库 /** * 获得所有系统功能 * @return array *

在wpf或winform关闭子窗口或对子窗口进行某个操作后刷新父窗口

父窗口: ///<summary>///弹出窗口 ///</summary>///<param name="sender"></param>///<param name="e"></param>privatevoid miFuncSet_Click(object sender, RoutedEventArgs e){WinFuncSetting funcSetting =new WinFuncS

Vue.js 获得兄弟元素,子元素,父元素(DOM操作)

e.target 是你当前点击的元素 e.currentTarget 是你绑定事件的元素 e.currentTarget.previousElementSibling.innerHTML 获得点击元素的前一个元素 e.currentTarget.firstElementChild 获得点击元素的第一个子元素 e.currentTarget.nextElementSibling 获得点击元素的下一个元素 e.currentTarget.getElementById("string") 获