大家好,这里记录,我每周读到的技术书籍、专栏、文章以及遇到的工作上的技术经历的思考,不见得都对,但开始思考总是好的。
用什么来区分程序员之间的能力
程序员A:我30分钟内,能写出200行代码;
程序员B:我30分钟内,能写出100行代码;
那么,你能说程序员A一定比程序员B的能力强吗?
在物理空间内的生产效率方面,有能力的人与没能力的人之间的差距最多也就几倍。但像程序员这种以信息空间为主战场的人,由于不受物理方面的制约,各个程序员之间的生产效率有很大的差别。据说能差30倍。
在同一个生产车间的两个流水线工人,如果他们在单位时间内安装零件的速度有差别,那么很明显他们的能力,就可以立马被识别出来。
但是,程序员是一个考虑创造性的工种,编程属于设计行为。
也因此,他们之间的能力水平,并不能简单的依靠“同样的时间内能写出多少代码”这样的生产效率来衡量。
到底,程序员之间的能力,可以依靠哪些方面来衡量呢?
有的人写出的代码能用,有人写出的代码不能用,根本运行不起来。
有的人写出的代码没有bug,有的人写出的代码到处都是bug,显然,两者的维护成本也会出现巨大差别。
有的人写出的代码执行速度快,有些人写出的代码执行速度慢。
有些人写出的代码可读性高,有些人写出的代码让人很难看懂。
有些人写出的代码便于修改,有些人写出的代码一修改就会出问题,也很显然,两者产生的优化成本也大不相同。甚至代码质量差到无法进行优化。
到这里,你就能看出来,有能力的程序员和没有能力的程序员,他们之间的差别在什么地方了,还能够看出来,有能力的程序员比没有能力的程序员强出好几个档次。
这里,要多说一点,上面提到了写性能快的代码和性能慢的代码,那么,是不是,我们刚开始写需求代码的时候,就一定要朝着性能快而去呢。
我个人认为倒是不见得,而应该是先写高质量的代码再调节性能效率,按照这个顺序会更佳。
在编程的时候,我们首先要注意的是代码的正确性和可读性,编写高质量的代码,而不是想法设法让代码运行速度变快。
当有一天需要面临提升代码性能的时候,再来进行性能优化,而且,有了之前高质量的代码作为基础,优化代码性能的时候,所要增加的工作也会轻松一些。
注意,这里说的 ,“高质量”和“高性能”,在大多数情况下并不矛盾。
代码必然会被修改,而且代码也会越来越没有秩序,而上面说的优化性能也属于代码修改的范畴。
因此,编写容易修改的代码,对程序员的所有能力要求中,永远是排在第一位的。
一名程序员如何具备这样的能力呢,那要首先看什么样的代码是高质量的代码。
网络上流传这样一个段子:
希望别人写的
别人希望我写的
我不想写的
就是高质量的
段子归段子,开心下就好,高质量代码的特征除了上面提到的那些,当然还有很多,耳熟能详的,可读性、可维护性、可扩展性、易用性、鲁棒性等等。
嗯,如何才能具备编写这样高质量代码的能力呢。
软件行业发展这么多年,如今,站在“老一辈”程序员的肩膀上,我们早就有了好多种“武功招式”和“内功心法”。
KISS
DRY
YAGNI
PIE
SLAP
SOLID
...
上面这些原则和思想,聚力使用,到了终点,你就会变成你期望的样子。
在这周的第一个主题结束之前,我给大家留下几段代码,大家可以用来阅读,看看它们最基础代码质量,哪一段更好。
代码段A-1:
代码语言:javascript复制public Long parseUmpActivityId(PlayApplyContext applyContext) { if (applyContext == null || applyContext.getPlayDetailDO() == null || StringUtil.isBlank(applyContext.getPlayDetailDO().getDetail())) { return null; } Map<String, String> playDetailMap = toPlayDetailMap(applyContext.getPlayDetailDO().getDetail()); if (playDetailMap == null) { return null; } String umpActivityIdStr = playDetailMap.get(Constant.UMP_ACTIVITY_ID); if (StringUtils.isBlank(umpActivityIdStr)) { return null; } return Long.parseLong(umpActivityIdStr);}
代码段A-2:
代码语言:javascript复制public Long parseUmpActivityId(PlayApplyContext applyContext) { return Optional.ofNullable(applyContext) .map(PlayApplyContext::getPlayDetailDO) .map(PlayDetailDO::getDetail) .map(this::toPlayDetailMap) .map(m -> m.get(Constant.UMP_ACTIVITY_ID)) .filter(StringUtils::isNotBlank) .map(Long::parseLong) .orElse(null);}
那一段代码好呢?
代码段B-1:
代码语言:javascript复制......
Result<Long> result = apply(juItem); if (result == null || !result.isSuccess()) { if (result != null && result.getMsg() != null) { return Result.buildErrorResult(result.getMsg()); } return Result.buildErrorResult("创建失败"); } ......
代码段B-2:
代码语言:javascript复制......
Result<Long> result = apply(juItem); if (result == null || !result.isSuccess()) { return Result.buildErrorResult( result != null && result.getMsg() != null ? result.getMsg() : "创建失败"); } ......
哪一段代码好呢?
程序员的时间都去哪儿了
一项研究表明:
软件产品的业界平均生产效率大约是每人每天10到50行最终交付代码。
可是,哪怕一名水平再低的程序员,敲出10到50行代码也就是50分钟,要不放开到2个小时,那一天剩余的时间都干嘛去了。
这一数据的背后原因,有一部分是把非程序员所花费的时间也计算在内了,比如测试人员、项目经理,甚至包括行政支持人员。另外,还有架构设计这样的工作,也在计算的时候被考虑进去了。
但,这都不是主要的原因。
最主要的原因是,程序员的主要活动没有在写代码上面,而是在调试以及修正那些无法正常工作的代码。
很多刚开始接触软件编程的开发者的梦都是在调试器面前破碎的。
作为一名程序员,每个人都想写代码,写新的代码,但是,却很讨厌调试他们写的代码。讨厌归讨厌,你写的代码总要你负责运行起来,“解铃还须系铃人”。
还有一项研究表明:
软件开发者的90%的时间都会消耗在苦苦探究“我的代码为什么不能正常工作”这个问题上。
所以,你天天想着只写新代码,是不现实的,因为真实的世界不是这样的。
另外,既然谈到了时间,在这个标题中的最后内容部分,我想再给大家引用些关于程序员的时间分配内容。
作为一个专业程序员,你必须让你的专业领域知识保持最新——就像是脑外科医生和飞行员都希望自己的领域专业知识保持最新一样。因此,你不能把你的晚上、周末和假期都用在项目加班上。你真的认为脑外科医生每周60小时都在做手术,飞行员每周60小时都在天上飞?当然不是了:工作准备和接受教育才是他们职业的核心部分。
压垮架构设计的两根稻草
如果说,最终有两根稻草能够压垮一个系统的架构设计,那么这两根稻草,一根是需求、另外一根就是访问量。
为什么这么说。
大家一定听说过“熵增定律”,刚开始创建的一个环境内,一切因素都在有条不紊、有序地运行,好景不长,后来逐渐地变得无序起来,这就是我们说的熵,也就是混乱了。
架构设计也符合这个定律,因为架构设计随着时间的推进和环境的演变也会混乱。
你设计好了一个系统,上线后,你不可能不接需求。
系统在不停地接需求,则表明业务在发展,这时,用户的访问量就可能会上来。
试想,3年前设计了一个系统架构,这期间如果我们不经常“修修补补”,如何来保障系统可以在不停地接需求和访问量变大的冲击下“安稳地”生存呢。
因为架构的本质就是:
通过合理的内部编排,保证系统高度有序,能够不断扩展,满足业务和技术的变化。
这里,有的同学可能会发出一个疑问:为什么不能在3年前就设计一个撑得过3年的架构呢。
我们确实可以遵循一定的的宏观设计方法,比如确定好系统以及模块之间的通讯方式,或者遵循微观的设计方法,比如通过预留扩展点的方式来尽可能的给未来的工作“留个位置”,但往往更多的细节仍然是需要结合当时的需求环境来做决定的。
另外架构也仅仅是架构。
架构落地的本质还是依靠代码的填充。
而代码变得越来越混乱是软件开发中自然而然的事情。
请相信这是真的。程序员的能力水平是有差距的。不管开头多么有序,只要过上一段时间,代码就会呈现出腐化的征兆,如果此时抓住了修改时机还好,不然,最终的结果,就是开头说的,逃不掉那个“熵”。
还有,软件架构从来都不是大型的预先设计,而是演进出来的。正因如此,才需要我们修修补补,如果你不对系统进行修补,欠下的债就会像贷款产生的利息一样,越来越多。
用户关心系统的什么
系统并不仅仅是一些事物的简单集合,而是一个由一组相互连接的要素构成的、能够实现某个目标的整体。从这一定义可见,任何一个系统都包括三种构成要件:要素、连接、功能和目标。--《系统之美》
《系统之美》里面所介绍的系统是一个广义的定义,泛指一切符合系统属性的内容体系,那么,软件工程里面的系统架构,所指的系统,自然也在这个系统的定义范围内。
软件系统架构里面的系统所定义要素是功能模块,连接是模块间的通信,功能当然就是模块所实现的功能,最后的目标就是系统要达到的整体效果,比如可扩展、稳定性等等。
用户关心什么。
肯定不会关心程序员的代码是否容易阅读,也不会关心代码修改起来是否容易,用户只会关心这个软件使用起来是否容易,比如一个购物的App,用户只会在意能否轻松找到他所需要的商品,能否很顺畅的进行付款操作,也就是用户只会关心软件系统的外在特性,外在特性是用户所唯一关注的特性。
那么,我们的程序员应该关心什么呢。
程序员除了要关心软件的外在特性之外,还要关心软件的内在特性,比如刚才提到的软件代码的可读性,代码是否有良好的结构,代码是否容易修改等等。
还有哪些内在特性呢。
可维护性
灵活性
可移植性
可重用性
可读性
可测试性
可理解性
细心的你,可能会发现这些内在特性有一定的重叠,比如可维护性和可读性,其实可维护性的代码都会有很好的可读性,但是它们在特定的场合有着不同的重要性。
在频繁的对接需求中,程序员肯定是希望可维护性要强,容易修改,就能很快的交付。当我们接收一个历史遗留的代码工程的时候,我们就很在意代码的可读性,代码越容易理解,就能够越快速的接手这套工程。
一个理想的开发环境应该有哪些角色。
包含功能开发人员、测试开发人员和用户开发人员。
功能开发人员负责开发用户所使用的产品的功能,测试开发人员协助功能开发人员编写测试代码以及提供测试框架,前两种角色,我们都很容易理解他们的职责。
那么对于用户开发人员,这种角色的职责主要是解决面向用户的任务,包括但不限于用户故事、用例以及探索测试等等。另外用户开发人员还要关心功能模块如何集成在一起成为一个完整的整体。
我说这是一种理想的开发环境,甚至是一种“乌托邦”,不存在。
当然,我的说法也不见得对。但是我经历了这么多研发环境里面,做单元测试和写单元功能均是由研发人员来完成,我认为他们本来就应该是一拨人。
早些年测试界出版了一本名字叫做《Google软件测试之道》的书籍,书中介绍了Google在这方面的人员组织形式,包括软件开发工程师(SWE),软件测试开发工程师 (SET),测试工程师(ET),其实这三种角色分别正是对应了功能开发人员,测试开发人员和用户开发人员。
但,这本书中也明确地讲到,Google的开发环境只是接近这种理想的环境。
另外,功能开发和测试开发,这两个开发动作里面虽然都有具体的编写代码的工作,但是它们的思维模式是完全不同的。
对于功能代码而言,思维模式是创建,重点在考虑用户、使用场景和数据流程上;而对于测试代码来说,主要思路是去破坏,怎样写测试代码用以扰乱分离用户及其数据。
恭喜你,又完成一次思考。
参考资料:
极客专栏文章《架构的本质:如何打造一个有序的系统》、《Google软件测试之道》、《代码大全》、《软件开发者职业生涯指南》、《编程的原则:改善代码质量的101个方法》、《程序员应该知道的97件事》
图自网络