美文网首页
关于写代码这件事

关于写代码这件事

作者: 罗胜金 | 来源:发表于2017-05-23 22:46 被阅读138次

    关于写代码这件事,我提出三个问题,并先答为敬:


    其一,什么样的代码是好代码?

    很炫的代码。
    每个函数都使用了STL。
    每个类都散发着设计模式的味道。
    每个处理流程都曲径通幽,需要读者焚香沐浴,静心细品,才能发现作者的匠心独运。
    这就是好代码吗?

    Oh no,也许不是这样的。
    首先代码能跑起来,实现用户的功能需求。然后再谈写得好不好。
    怎么算好呢?

    简洁明了
    代码(函数,类)功能单一

    一个函数只干一件事情,一个类只抽象一个明确的主题。不要只盯着面向对象里面有一个SRP(单一职责原则),实际上,不管是面向过程,也不管是面向对象还是函数式编程,内聚是永远不会错的。

    又想上班,又想炒股又想做生意,结果可能是什么也做不好。
    又接收报文,又解析报文,又校验报文,又存库,又组装报文,又发送报文,全铺开在一个函数里面,结果这个函数就变成了脏乱差。

    想想我们的项目代码里面,有多少用鼠标滚轮、总也滚不到结尾的函数?这是病,得治啊。

    实际上,大部分的函数,按功能封装封装,接收和解析报文提取一个函数,校验报文一个函数,组装报文一个函数,发送报文一个函数,就能弄得汤清水净,各个函数都功能单一了。

    只有很少的机会,才会碰到需要我们拆分接口,或者按聚合、组合还是继承关系划分出几个类,才能实现功能单一的情况。

    简单就是美。

    代码干净利落,不拖泥带水

    如果功能单一,那么干净利落已经做到大半。
    这个标题下是想专门说说头文件的包含。

    生成目标文件越来越大。宏的重复定义、编译告警越来越多。
    头文件包含另外的一堆头文件。源文件包含所有能想到的头文件。
    有时候编译不过去,要把源文件里面#include的a.h放到b.h后面,才能编过去。

    你确定,你真的需要这么多头文件吗?
    你知道,编译器会把你#include的所有头文件的每行代码,都铺开并占领到你的源文件中,一起参与编译,因此你编译的代码行数,并不仅仅是你的源文件代码行数这么少吗?
    拜托,不需要的头文件,别#include进来吧。
    尤其是,尽量别在头文件中包含头文件好吗,亲?

    变量、函数、类命名清晰、准确

    这个本来是不消说的。

    生个娃,取个好名字能让娃今后万事如意,这是所有家长都懂的了。

    定义个变量,写个函数,创建一个类,写个宏,如果从名字根本看不出来是用作啥的,那读者看着多闹心。

    同一个文件中的函数名:有的全小写,用下划线连起来各个单词,这是linux风格的命名;有的大小写相间,用大写字母分开各个单词,这是骆驼命名法;有的又有下划线,又有大小写,好像雨天披着雨衣还同时打着伞,这是混搭命名法。总体看这是不好的命名。

    按编码规范,整齐统一,像给娃取名那样,谨慎的命名自己的变量、函数、类、宏的,这才是极好的。

    表达到位
    代码的意图清楚

    意图清楚,就是读者一看代码,就知道是干什么的。
    代码首先是给人看懂,其次才是给机器看。

    简洁明了,是意图清楚的前提。

    注释恰当,不多不少。
    不要故弄玄虚。不要使用不必要的STL。不用使用不必要的复杂算法。
    可以用简单数组搞定的,就不用指针、堆内存。

    命名多用set、get、check这些标准单词。
    更主要的,如果你非要大段注释才能说明白一段代码,这个代码多半需要重构。
    你可以把一组、甚至一条语句提取为一个小函数,只为让函数名称说明这是要干啥。

    提取函数.PNG
    代码的处理逻辑层次分明

    写代码和写文章是一样一样的。
    谋篇布局,主次分明,逻辑清楚,才好看。

    如果你实在没啥逻辑,就给代码标上1、2、3、4吧。
    写文章可以有章节编号,1,2,3,4,写代码为什么不能有?
    真的可以有:

    代码标号.PNG
    代码的处理逻辑,与问题领域的概念、规则吻合

    这个是比较高的境界。可意会。言传就落了俗套。
    与问题领域吻合,意思是代码的样子,符合我们对代码所描述的事物的直观认识。
    非高手不能。非多次重构不能。

    举个例子。

    保龄球记分,领域规则就是,全中得分为本轮加后两次,补中得分为本轮加后一次,普通得分为本轮。
    好的代码,主函数就展现了它的领域规则。

    保龄球记分.PNG
    安全可靠
    代码运行状态、时序管理得当

    尽量保证函数是可重入的。尽量不用全局变量。
    独占资源(锁定数据库、占用信号量)的时间尽量短。

    不滥用assert。传入数据不合要求你就挂起,真的合适吗?
    避免时序引起的偶发故障。别人的进程起来得比平时慢了,你就可以出错吗?

    代码具有必要的测试安全网络

    TDD、ATDD都是不错的选择。
    对于嵌入式软件,上设备调试太慢。找设备太难。
    还是PC仿真最方便。

    用例跑完一遍要快。秒级最好,分钟也行,不超过10分钟可以接受。为啥?用例跑得太慢,就没人爱用,没人爱用,修改代码就不安全了呢。

    改完代码,跑一遍用例。喝口茶,等着VC打印最后的OK。享受。

    易于扩展
    扩展新功能时,只需新增代码,无需改动老代码

    不要只盯着面向对象里面有一个OCP(开放-封闭原则),实际上,不管是面向过程,也不管是面向对象还是函数式编程,不动老代码、也能扩展新功能,都是一种追求。
    老代码都测试过了,能不动当然好啊。

    容易做到吗?不容易。
    但至少,你不能加一个小功能,都要翻天覆地改一遍吧?那只能说明你原来的设计太逊了。

    C语言里面,函数功能单一化,在易变的地方采用注册回调机制、表驱动机制,都可以把改动限制在局部,保持老代码的稳定。
    C++里面,工厂模式,让你创建新子类的对象时,不用改动使用这个对象的客户类代码;策略模式,让你面对不同产品的处理差异,不用改动基类和客户类代码,这都是一些具体办法。


    其二,代码是资产还是债务?

    黄蓉每天写500行代码。郭靖每天写100行代码。
    黄蓉比郭靖厉害?
    都是完成相同的功能,慕容复拥有2000行代码。乔峰只拥有200行代码。
    慕容复比乔峰厉害?

    用户要的是代码还是功能?

    代码能按行收钱吗?
    古龙写武侠小说,经常是一句话一行,一行一段。据说那时候台湾小说界是按行付稿费的。
    问题是代码不是小说。

    代码的目的是实现用户需求。
    在满足用户需求的前提下,我们的代码应该——
    尽可能少。
    看看下面的代码。这样的代码,大量出没在超过10000000行的软件项目中。

    复杂代码.PNG

    维护这些代码,得花多少人力?
    维护者是否会觉得上辈子欠了原作者的债呢。
    公司和项目是否都在背着这些代码的债务,负重前行呢?
    有些严谨又有趣的老外,好像已经把这个债务算清了:
    Technical Debt Is Now Costing Us $3.61 Per Line Of Code(“CAST估计,现在公司要解决技术负债的花费,是每行代码3.61美元。”). -- Jonathan Bloom From CAST.


    其三,代码只是设计的实现吗?

    据说程序员都是码农。
    真正的大牛只画UML图,偶尔提出一些软件思想和理论,供码农们崇拜。
    码农们像蚂蚁那样coding,给别人的设计来作嫁衣裳。
    真的是这样吗?

    软件工程的理论基础,是和传统工程学做对比,尤其是和建筑业做对比。
    设计主要是画图,好比画建筑图纸;实现就是coding,好比砌砖。
    真的是这样吗?

    建筑师画图纸,民工砌砖。没听说哪个民工砌砖砌得好变成了建筑师,没听说建筑师以前都是砌砖的。
    但软件SE都是从coding过来的,而且必须是coding很好的程序员。
    为什么呢?

    从程序员到架构师.PNG

    所以,软件工程,无法解释软件系统工程师的来源问题。
    如果说——代码也是设计,程序员编程的过程,也属于设计的过程。那就好解释了——
    作为程序员,你一直在设计,设计能力提高了,你就成了软件SE。

    工程活动分成设计和生产。
    在软件开发中,编程完全属于设计活动。我们通常说的软件SE“设计”的过程,也是软件设计的一部分。
    软件里的生产工人,就是编译器。
    软件的测试和调试,也是设计的一部分,是设计的验证。

    如果认可这一点,除了自豪感油然而生,是否你也应该把代码写得更好点?


    上述观点,纯属作者自言自语。
    作为有思想的程序员,你不必同意作者的观点。

    你只需想想,对这三个问题,你从前是怎么看的、现在又怎么看,这么多年过去,自己的看法是否有变化、是否有进步,就OK了。

    相关文章

      网友评论

          本文标题:关于写代码这件事

          本文链接:https://www.haomeiwen.com/subject/hcngxxtx.html