【读书笔记】- 代码整洁之道

代码整洁之道

概要:

杂谈

分章总结

--------------------------------------------------------------------------------------------

杂谈

     谈及代码整洁,大的方面是关于一个系统的演变后的模样,好比混沌的创世,到有了法治文明。小的方面是家庭的标准、家规等等。例如系统间的协议、约定、通用性等。合理的方式都是整洁的一种表现。而非整洁就会让系统面目全非。越到后面越难以维护。只能走重构的路,可是真的有能力重构吗?人员的流失,对业务的掌握并非是知识的能及范围。可能导致事故。所以不断的code review是必要的。在过程中去沉淀,思考。而不是让系统跑起来就是完工,是思考系统如何以一种优雅的方式运行,去接收新新需求,但要预防过度设计,夸夸其谈未来需求。改进的道路是基于历史的痕迹上构建的。能实现高内聚的时候就组合成团,组件化。还不成熟的,要清晰易懂,不要写反逻辑、过多的抽取,可能有些是共用的,但是想想收益真的好嘛。书中给的基础的。常识性的一定要遵守,这是公认的,也是基础。在基础上的任何扩展,只要满足本质目标,都是正确的。下面是章节觉得有价值的摘录。小记一下。

--------------------------------------------------------------------------------------------

分章总结

第一章:整洁代码

艺术书并不能保证你度过之后能成为艺术家,只能告诉你其他艺术家用过的工具、技术和思维过程。本书同样也不能担保让你成为好程序员。它所能做到的,只是展示好程序员的思维过程。还有他们使用的技巧、技术和工具。

第二章:有意义的命名

1、名副其实
    降低代码的模糊度。
    减少魔术数的使用。
    减少误导性名称的使用。(相似词汇、a1,a2...)
    降低废话的使用(废话是另一种没有意义的区分。假设你有一哥Product类,如果还有一个ProductInfo或者ProductData类,那么他们的名称虽然不同,意义却无区别。info和data就像a、an和the一样,是意义含糊的废话)
        举例:moneyAmount就与money没区别
    使用读得出来的名称
    使用可搜索的名称
    明确是王道
    类名应该是名词或名词短语。如Customer、WiliPage、Account避免使用Manager、Processor、Data或Info这类名。类名不应该是动词。
    方法名应该是动词或动词短语。如postPayment、save。属性访问器、修改器和断言应该根据其值命名。并依照JavaBean标准加上get、set和is前缀。
    每个概念对应一个词。
    别用双关语
    只有程序猿才会读你的代码。尽量使用那些计算机科学术语、算法名、模式名。
    同一个变量的含义不要在系统中出现类似名称要统一,统一,统一!!!


第三章:函数

    函数的第一规则要短小。第二条规则是还要更短小。那么多少算小。个人理解,函数表意。达到描述含义,函数自然不会大,最小单元。
    函数应该做一件事。做好这件事,只做这一件事。总览型(步骤)方法前可加TO。
    函数参数:最理想的参数数量是零、其次是一、再次是二、尽量避免三。参数不易对付,它们带有太多概念性。
    函数要么做什么事,要么回答什么事,但二者不可兼容。
    错误处理就是一件事。
    依赖磁铁(如错误码)慎用。
    
    如何写出这样的函数:
        一开始必然是冗长而复杂的。(避免提前抽象);配合单元测试。在OK以后,进行方法拆解。满足以上原则,打磨,分解函数,修改名称,消除重复。缩短和重新安置方法。有时会拆散类,同时保持测试通过。
    大师级程序员把系统当做故事来讲,而不是当做程序来写。他们使用选定的编程语言提供的工具构建一种更为丰富且更具表达力的语言,用来讲那个故事。


    
第四章:注释

    别给糟糕的代码加注释------重新写吧。                Brian W.Kernighan 与 P.j.plaugher

    什么也比不上放置良好的注释来得有用。什么也不会比乱七八糟的注释更有本市搞乱一个模块。什么也不会比陈旧、提供错误信息的注释更有破坏性。
    过多的注释,并不“纯粹的好”。注释更多也就是一种必要的恶。若编程语言足够有表达力,或者我们长于用这些语言来表达意图,就不那么需要注释------也许根本不需要。
    不准确的注释要比没注释坏的多。真是只在一处地方有:代码。
    注释不能美化糟糕的代码。带有少量注释的整洁而由表达力的代码,要比带有大量注释的零碎而复杂的代码像样的多。与其花时间编写解释你搞出来的糟糕的代码的注释,不如花时间清洁那堆糟糕的代码。
    用代码来阐述,而不是用注释。
    有用的注释:晦涩难明的参数或返回值的意义翻译为某种可读形式。 
    注释可以用来放大某种看来不合理之物的重要性。
    直接把代码注释掉是讨厌的做法。其他人不敢删除注释掉的代码。


第五章:格式

    应该选用一套管理代码格式的简单规则,然后贯彻这些规则。
    代码的可读性可以对后续的修改行为产生深远影响。原始代码修改之后很久,其代码风格和可读性扔会影响到可维护性和扩展性。
    变量声明应该尽可能靠近其使用位置。
    
    下面列举两段代码,你会喜欢哪种呢?
    int canReNum = 0;
    for (ReInfo reInfo : ReInfoList) {
        if (xxxInfo.getId() == ReConstant.RE_EI_ID) {
            canReNum = reInfo.getCanReNum();
        }else{
            String did = reInfo.getId() + "_" + reInfo.getSid();
            if(NumCOMap_ds.containsKey(did)){
                NumCO NumCO = NumCOMap_ds.get(did);
                NumCO.setReNum(reInfo.getCanReNum());
            }
        }
    }

    for (Map.Entry<String, NumCO> entry : NumCOMap_ds.entrySet()) {
        if (canReNum != 0) {
            NumCO NumCO = entry.getValue();
            NumCO.setReNum(canReNum);
        }
    }

------------------------------------------------------------------------    
    
    int canReNum = 0;
    for (ReInfo reInfo : ReInfoList) {
        if (xxxInfo.getId() == ReConstant.RE_EI_ID) {
            canReNum = reInfo.getCanReNum();
            continue;
        }
            
        String did = reInfo.getId() + "_" + reInfo.getSid();
        if(NumCOMap_ds.containsKey(did)){
            NumCO NumCO = NumCOMap_ds.get(did);
            NumCO.setReNum(reInfo.getCanReNum());
        }
        
    }

    for (Map.Entry<String, NumCO> entry : NumCOMap_ds.entrySet()) {
        if (canReNum != 0) {
            NumCO NumCO = entry.getValue();
            NumCO.setReNum(canReNum);
        }
    }
--------------------------------------------------------------------------    


第六章:对象和数据结构

    得墨忒耳律:模块不应了解它所操作对象的内部情形。类C的方法F只应该调用以下对象的方法:
        C
        由f创建的对象;
        作为参数传递给f的对象;
        由C的实体变量持有的对象。
    方法不应调用由任何函数返回的对象的方法。
    对象暴露行为,隐藏数据。便于添加新对象类型而无需修改既有行为,同时也很难以在既有对象中添加新行为。
    数据结构暴露数据,没有明显行为。便于向既有数据结构添加新行为,同时也很难向既有函数添加新数据结构。

第七章:错误处理

    避免方法返回null。
    别传递null。
    

第八章:边界


    学习性测试。


第九章:单元测试


    整洁测试的要素:可读性、可读性、可读性。
    每个测试一个概念
    FIRST规则:快速fast、独立、可重复、自足验证、及时
    代码组织上


    
第十章:类


    类应该短小;
    类的名称应当描述其权责;
    内聚;


第十一章:系统


    依赖注入
    工厂创建
    DI好处,需要对象之前并不构造对象。其次,许多这类容器提供调用工厂或构造代理的机制,而这种机制可以延迟赋值或类似的优化处理所用。
    “一开始就做对系统”纯属神话。反之,我们应该只去实现今天的用户故事,然后重构,明天再扩展系统,实现新的用户故事。这就是迭代和增量敏捷的精髓所在。测试驱动开发、重构以及它们打造出整洁代码,在代码层面保证了这个过程的实现。但是在系统层面如何?难道系统架构不需要预先做好计划吗?系统理所当然不可能从简单递增到复杂,它能行吗?
    软件系统与物理系统可以类比。它们的架构都可以递增式地增长,只要我们持续将关注面恰当地切分。

第十二章:迭代


第十三章:并发编程


    并发:
        并发会在性能和编写额外代码上增加一些开销;
        正确的并发是复杂的,即便对于简单的问题也是如此;
        并发缺陷并非总能重现,所以常被看做偶发事件而忽略,未被当做真的缺陷看待;
        并发常常需要对设计策略的根本性修改。
    并发防御原则:
        单一权责原则
            并发相关代码有自己的开发、修改和调优生命周期;
            开发相关代码有自己要对付的挑战,和非并发相关代码不同,而且往往更为困难;
            即便没有周边应用程序增加的负担,写得不好的并发代码可能的出错方式数量也已经足具挑战性。
            建议:分离并发相关代码与其他代码。
        限制数据作用域
            谨记数据封装;严格限制对可能被共享的数据的访问。
        使用数据复本
            避免共享数据的好方法之一就是一开始就避免共享数据。在某些情形下,有可能复制对象并以只读方式对待。在另外的情况下,有可能复制对象,从多个线程收集所有复本的结果,并在单个线程中合并一些结果。
        线程应尽可能地独立
            让每个线程在自己的世界中存在,不与其他线程共享数据。每个线程处理一个客户端请求,从不共享的源头接纳所有请求数据,存储为本地变量。这样一来,每个线程都像是世界中的唯一线程,没有同步需要。
    了解java库:并发包
        了解执行模型:
        概念:基础定义
            限定资源:并发环境中有着固定尺寸和数量的资源。例如数据库连接和固定尺寸读写缓存等
            互斥:每一时刻仅有一个线程能访问共享数据或共享资源
            线程饥饿:一个或一组线程在很长时间内或永久禁止。例如,总是让执行得快的线程先运行,假如执行得快的线程没完没了,则执行时间长的线程就会“挨饿”。
            死锁:两个或多个线程相互等待执行结束。每个线程都拥有其他线程需要的资源,得不到其他线程拥有的资源,就无法终止。
            活锁:执行次序一致的线程,每个都想要起步,但发现其他线程已经“在路上”,由于竞步的原因,线程会持续尝试起步,但在很长时间内却无法如愿,甚至永远无法启动。
        生产者-消费者模型
            一个或多个生产者线程创建某些工作,并置于缓存或队列中。一个或多个消费者线程从队列中获取并完成这些工作。生产者和消费者之间的队列是一种限定资源。
        读者-作者模型
            当存在一个主要为读者线程提供信息源,但只偶尔被作者线程更新的共享资源,吞吐量就会是个问题。增加吞吐量,会导致线程饥饿和过时信息的累积。更新会影响吞吐量。协调读者线程,不去读作者线程正在更新的信息,这是一种辛苦的平衡工作。作者线程倾向于长期锁定许多读者线程,从而导致吞吐量问题。
            挑战之处在于平衡读者线程和作者线程的需求,实现正确操作,提供合理的吞吐量,避免线程饥饿。
        宴席哲学家
            想象一下,一群哲学家环坐在圆桌旁。每个哲学家的左手边放了一把叉子。桌面中央摆着一大碗意大利面。哲学家们思索良久,直到肚子饿了。每个人都要拿起叉子吃饭。但除非手上有两把叉子。否则就没法进食。如果左边和右边的哲学家已经取用一把叉子,中间这位就得等到别人吃完,放回叉子。每位哲学家吃完后,就将两把叉子放回桌面,直到肚子再饿。
            用线程代替哲学家,用资源代替叉子,就变成了许多企业级应用中进程竞争资源的情形。如果没有用心设计,每种竞争式系统就会遭遇死锁、火锁、吞吐量和效率降低等问题。并发问题大多都是这三个问题的变种。建议学习这些基础算法。理解其解决方案。

第十四章:逐步改进


    代码真的很有意思,可以和生活中很多事情类比,例如城市建设。在这么一个庞大的系统中,开始可能只是考虑一个人生存的居所,然后呢,人口多了,有了文明、有了丰富的物质后,例如拥挤、混乱产生、代码结构混乱了,僵尸代码产生了,业务增长后,有些代码也很难剥离。怎么办?城市化改造依然艰难,棚户区改造。那么只能区域自治。一点点的小范围重构,但是也会发现,小范围可能因为高度不够,也不能达到最终目标(不代表小范围重构的失败,这只是一小步)。所以,需要重梳理,模块划分边界,然后整合边界,重新规划模块,在做组合。这是一个熟悉到了解再到掌握的过程。基于持续性发生的业务,去融合重构的模式或者骨架,去适应。而骨架可能是多个骨架,如一国两治,一系统多组合。
    重构的手段,保持运转一致。建议采用测试驱动开发的规程。
    
    腐蚀的渗透性,代码能工作还不够,能工作的代码经常会严重崩溃。满足于仅仅让代码能工作的程序猿不够专业。他们会害怕没时间改进代码的结构和设计,我不敢苟同。没什么能比糟糕的代码给开发项目带来更深远和长期的损害了。进度可以重订,需求可以重新定义,团队动态可以修正。但糟糕的代码只是一直腐败发酵,无情地拖着团队的后腿。工作这多年,确实看到了一些这样的代码,团队蹒跚前行,只因为他们匆匆搞出一片代码沼泽,从此以后命运再也不受自己控制。当然,糟糕的代码可以清理。不过成本高昂。随着代码腐败下去,模块之间相互渗透,出现大量隐藏纠结的依赖关系。找到和破除陈旧的依赖关系又费时间又费劲。解决之道,保持代码整洁和简单。
    

第十五章:综合


    注释:
        不恰当的信息:让注释传达本该更好地在源代码控制系统、问题追踪系统或任何其他记录系统中保存的信息,是不恰当的。注释只应该描述有关代码和设计的技术性信息。
        废弃的注释:过时、无关或不正确的注释就是废弃的注释。
        冗余注释:如果注释描述的是某种充分自我描述了的东西,那么注释就是多余的。注释应该谈及代码自身没提到的东西。
        糟糕的注释:值得编写的注释,也值得好好写。
        注释掉的代码:看到被注释掉的代码,真是令人抓狂的一件事。谁知道它有多旧?谁知道它有没有意义?没人会删除它,因为大家都假设别人需要它或是有进一步计划。那样的代码就这样腐烂掉,随着时间推移,越来越与系统没关系。它调用不复存在的函数,它使用已改名的变量。它遵循已被废弃的约定。它污染了所属的模块,分散了想要读它的人的注意力。注释掉的代码纯属厌物。看到就删除吧,假如需要,版本代码会查询到的。
    函数:
        函数的参数应该尽量少。没参数最好。
        标识参数:布尔值参数大声宣告函数做了不止一件事,令人迷惑,应该消灭掉。
        死函数:永不被调用的方法应该丢弃。
    一般性问题:
        遵循“最小惊异原则”,函数或类应该实现其他程序猿有理由期待的行为。
        不正确的边界行为:代码应该有正确的行为,这话看似明白。问题是我们很少能明白正确行为有多复杂。别依赖直觉。
        封装条件:抽离if条件,如果有多条件。
        避免否定式条件。否定式要比肯定是难明白一些。
        函数只该做一件事
        用命名常量代替魔术数
        存在时序情况,避免掩蔽时序耦合,正确的方式是产生结果作为下一个时序的入参或条件进行。显示时序。
        在较高层级放置可配置数据
        不要继承常量
    名称:
        采用描述性名称
        为较大作用范围选用较长名称
        名称应该说明副作用
    死锁发送的四个条件:
        互斥:当多个线程需要使用同一资源,且这些资源满足下列条件时,互斥就会发生。
            无法再同一时间为多个线程所用;
            数量上有限制。
        这种资源的常见例子是数据库连接、打开后用于写入的文件、记录锁或信号量。
        上锁及等待:
            当某个线程获取一个资源,在获取到其他全部所需资源并完成其工作前,不会释放这个资源。
        无抢先机制:
            线程无法从其他线程处夺取资源。一个线程持有资源时,其他线程获得这个资源的唯一手段就是等待该线程释放资源。
        循环等待:
            “死命拥抱”。想象两个线程,T1和T2,还有两个资源R1和R2,T1拥有R1,T2拥有R2,。T1需要R2,T2需要R1。如此就出现了环状依赖。
    这四种条件都是死锁必须的,只要其中一个不满足,死锁就不会发生。    
    
    避免死锁:
        避免死锁的一种策略是规避互斥条件。
            可以使用允许同事使用的资源,如:AtomicInteger;增加资源数量,使其等于或大于竞争线程的数量;在获取资源之前,检查是否可用。
            不幸的是,多数资源都有上线,且不能同时使用。而且第二个资源的标识也常常依据对第一个资源的操作结果来判断。
        不上锁及等待:
            如果拒绝等待,就能消除死锁,在获取资源之前,检查资源,如果遇到某个繁忙资源,就释放所有资源,重新来过。
            这种手段带来的潜在问题:
                线程饥饿:某一个线程一致无法获得它所需的资源(它可能需要某种很少能同时获得的资源组合)
                活锁:几个线程可能会前后相连地要求获得某个资源,然后再释放一个资源,如此循环。这在单纯的CPU任务排列算法中尤其有可能出现
            二者都能导致较差的吞吐量。第一个的结果是CPU利用率低,第二个的结果是较高但无用的CPU利用率。
        满足抢先机制:
            避免死锁的另一种策略是允许线程从其他线程上夺取资源。这通常利用一种简单的请求机制来实现。当线程发现资源繁忙,就要求其他拥有者释放。如果拥有者还在等待其他资源,就释放全部资源并重新来过。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值