ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

代码整洁之道读后感

2021-04-27 20:34:31  阅读:134  来源: 互联网

标签:count 读后感 String 代码 private 之道 整洁 page 函数


代码整洁之道

序章

TPM

全员生产维护(Total Productive Maintenance),作为质量保证手段,他所提倡维护的关注度甚至要优于生产,好的维护性,才是系统能持续发展的基石。

5S原则

  • 整理(Seiri),搞清楚事物之所在,注重命名需要合理恰当。
  • 整顿(Seiton),物皆有其位,而后物尽归其位,注意代码罗列的位置。
  • 清楚(SeiSo),及时清理无用代码。
  • 清洁(Seiketsu),保持工作地清洁的组内共识,代码风格很重要。
  • 身美(Shitsuke),自律与执行力,对制定的标准应该严格落实。

整洁代码

破窗理论

窗户破损了的建筑让人觉得似乎无人照管。于是别人也再不关心。他们放任窗户继续破损。最终自己也参加破坏活动,在外墙上涂鸦,任垃圾堆积。一扇破损的窗户开辟了大厦走向倾颓的道路。

building with broken windows looks like nobody cares about it. So other people stop caring. They allow more windows to become broken. Eventually they actively break them. They despoil the facade with graffiti and allow garbage to collect. One broken window starts the process toward decay.

优雅代码重要顺序

  • 能通过所有测试。

  • 没有重复代码。

    • 如果同一段代码反复出现,就表示某种想法未在代码中得到良好的体现。我尽力去找出到底那是什么,然后再尽力更清晰地表达出来。
  • 体现系统中的全部设计理念。

  • 包括尽量少的实体,比如类、方法、函数等。

命名

命名规范不在于代码长度

不要认为代码简短就可以随意命名,一定要遵守规范,否则简短的代码,也会有很大的阅读成本。

public List<int[]> getThem() {
    List<int[]> list1 = new ArrayList<int[]>();
    for (int[] x : theList)
        if (x[0] == 4)
            list1.add(x);
    return list1;
}

例如这段代码,Coding Style 规范,变量和逻辑都很少,但是就是阅读起来很困难。

这说明有的问题不在于代码的简洁度和整齐度,而是在于代码的模糊度:即上下文在代码中未被明确体现的程度。上列代码要求我们了解类似以下问题的答案:

  • theList 中是什么类型的东西?
  • theList 零下标条目的意义是什么?
  • 值 4 的意义是什么?
  • 我怎么使用返回的列表?

命名规范对于阅读效率的提升

例如上述代码,在业务中完全可以通过具体的业务含义赋予它们更有意义的名字。

原书示例:

/** 原版 */
public List<int[]> getThem() {
    List<int[]> list1 = new ArrayList<int[]>();
    for (int[] x : theList)
        if (x[0] == 4)
            list1.add(x);
    return list1;
}

/** 改进版 */
public List<int[]> getFlaggedCells() {
    List<int[]> flaggedCells = new ArrayList<int[]>();
    for (int[] cell : gameBoard)
        if (cell[STATUS_VALUE] == FLAGGED)
            flaggedCells.add(cell);
    return flaggedCells;

/** 最佳方案 */
public List<Cell> getFlaggedCells() {
    List<Cell> flaggedCells = new ArrayList<Cell>();
    for (Cell cell : gameBoard)
        if (cell.isFlagged())
            flaggedCells.add(cell);
    return flaggedCells;
}
}

可以看到,我们已经变得能够轻松的知道代码所表达的意义了。

我们不用去翻阅其他代码,了解这段代码块的前世今生,它本身就已经包含了我们阅读所需要的上下文条件。

一段好的代码甚至不需要注释来辅助,就可以让你了解到它的工作内容。

命名不要具有误导性

不要让你的代码,容易被人误会,命名最好能在词意通顺的前提下,做到能让人一目十行的畅快感。

示例:

/** o、l 与 0、1 就是最好的例子 */
int a = l;
if (O == l)
    a = O1;
else
    l = 01;

做有意义的区分

示例:

/** 原版 */
public static void copyChars(char a1[], char a2[]) {
    for (int i = 0; i < a1.length; i++) {
        a2[i] = a1[i];
    }
}

/** 改进版 */
public static void copyChars(char source[], char destination[]) {
    for (int i = 0; i < source.length; i++) {
        source[i] = destination[i];
    }
}

类名规范

类名和对象名应该是名词或名词短语,如 Customer、WikiPage、Account 和 AddressParser。避免使用 Manager、Processor、Data 或 Info 这样的类名。类名不应当是动词。

方法名规范

  • 方法名应当是动词或动词短语,如 postPayment、deletePage 或 save。属性访问器、修改器和断言应该根据其值命名,并依 Javabean 标准加上 get、set 和 is 前缀。

    string name = employee.getName();
    customer.setName("mike");
    if (paycheck.isPosted())…
    
  • 重载构造器时,使用描述了参数的静态工厂方法名。建议采用如 Complex.FromRealNumber(23.0) 代替 new Complex(23.0)。必要时甚至可以考虑将构造器设置为 private 强制采用该方式命名。

描述事物

对单一概念和单一事物的描述,命名应该时全局统一的。例如很多近义词,如 fetchretrieveget 统一从中选出一个代表,来指代系统中的相关概念,不要做无意义的不统一,除非有本质区别。

避免双关语

遵循“一词一义”规则

比如,在多个类中都有 add 方法,该方法通过增加或连接两个现存值来获得新值。假设要写个新类,该类中有一个方法,把单个参数放到群集(collection)中。该把这个方法叫做 add 吗?这样做貌似和其他 add 方法保持了一致,但实际上语义却不同,应该用 insert 或 append 之类词来命名才对。把该方法命名为 add,就是双关语了。

给变量提供充分的语境

示例:

/** 原版 */
private void printGuessStatistics(char candidate, int count) {
    String number;
    String verb;
    String pluralModifier;
    if (count == 0) {
        number = "no";
        verb = "are";
        pluralModifier = "s";
    } else if (count == 1) {
        number = "1";
        verb = "is";
        pluralModifier = "";
    } else {
        number = Integer.toString(count);
        verb = "are";
        pluralModifier = "s";
    }
    String guessMessage = String.format(
            "There %s %s %s%s", verb, number, candidate, pluralModifier
    );
    print(guessMessage);
}

/** 改进版 */
public class GuessStatisticsMessage {
    private String number;
    private String verb;
    private String pluralModifier;
    public String make(char candidate, int count) {
        createPluralDependentMessageParts(count);
        return String.format(
                "There %s %s %s%s",
                verb, number, candidate, pluralModifier);
    }
    private void createPluralDependentMessageParts(int count) {
        if (count == 0) {
            thereAreNoLetters();
        } else if (count == 1) {
            thereIsOneLetter();
        } else {
            thereAreManyLetters(count);
        }
    }
    private void thereAreManyLetters(int count) {
        number = Integer.toString(count);
        verb = "are";
        pluralModifier = "s";
    }
    private void thereIsOneLetter() {
        number = "1";
        verb = "is";
        pluralModifier = "";
    }
    private void thereAreNoLetters() {
        number = "no";
        verb = "are";
        pluralModifier = "s";
    }
}

函数

短小

if 语句、else 语句、while 语句等,其中的代码块应该只有一行。该行大抵应该是一个函数调用语句。这样不但能保持函数短小,而且,因为块内调用的函数拥有较具说明性的名称,从而增加了文档上的价值。

这也意味着函数不应该大到足以容纳嵌套结构。所以,函数的缩进层级不该多于一层或两层。当然,这样的函数易于阅读和理解。

单一职责

函数应该做一件事。做好这件事。只做这一件事。

如果函数能够被合理的切分为多个逻辑块,那么请拆分重构它。

抽象层级

每个函数应该有自己的抽象层级,只对自己层级的内容进行处理,并向下驱动更低层级。使函数的嵌套保留层级感。

函数中混杂不同抽象层级,往往让人迷惑。读者可能无法判断某个表达式是基础概念还是细节。更恶劣的是,就像破损的窗户,一旦细节与基础概念混杂,更多的细节就会在函数中纠结起来。

switch

学会使用多态来处理 switch 逻辑,在框架层通过工厂模式,根据 switch 来派生不同的实现类。

描述性的命名

一个长而包含叙述内容的函数名,往往要比一段冗长的描述性注释要好得多,我们大多数时间,都是在 IDE 中筛选函数而不是去直面注释,一个好的名字可以让你的函数更受人欢迎。

函数参数

函数的参数数量需要尽可能少,不然不论是后期维护,还是覆盖性测试,都很难处理。尤其是测试用例的编写难度会随参数数量指数级上升。

学会利用参数个数和返回值,来描述你的函数功能。

对于一些相关的参数,最好进行封装后再传递,通过类的传递能赋予参数良好的上下文环境,有利于代码逻辑的梳理。

控制副作用

应该尽量避免进行函数声明中所未提及的操作,避免一些时序性耦合与顺序依赖。

例如:

public class UserValidator {
    private Cryptographer cryptographer;
    public boolean checkPassword(String userName, String password) {
        User user = UserGateway.findByName(userName);
        if (user != User.NULL) {
            String codedPhrase = user.
                    getPhraseEncodedByPassword();
            String phrase = cryptographer.decrypt(codedPhrase, password);
            if ("Valid Password".equals(phrase)) {
                Session.initialize();
                return true;
            }
        }
        return false;
    }
}

上述函数看似逻辑清晰,但是它默默的进行了,Session.initialize() 的操作。这种问题在实际开发中,是很难被发现的。如果非要进行这样的封装,也应该将函数名声明为 checkPasswordAndInitializeSession 我们需要让自己的代码,告诉别人它要做的全部事情。避免给系统运行添加意料之外的问题。

尽量避免输出函数,哪怕只是封装一层也好,会对可读性有很大的提升。因为输出函数,大部分时候意味着你不得不去看他那冗长的注解与函数的声明和实现。

分离指令与实现

改变状态和返回对象信息,应该时俩件事!我们不要把俩件事放在一个函数内来做,不然很难用合适的词汇来描述这个函数的工作内容,可读性也会被破坏。

例子:

/** 如果含有 xxx 属性,就为 xxx 属性赋值 */
if (set("username", "unclebob"))…

/** 对上述函数拆分,很好的提高了可读性,至少你不用去思考,set 函数的 boolean 返回值,究竟代表什么了 */
if (attributeExists("username")) {
    setAttribute("username", "unclebob");
    …
}

用异常来代替错误码

使用派生出来的异常类,来替换错误码中的枚举,也是多态的一种实践。它遵守了开闭原则,清除了混乱的 ifelse 逻辑,已经有好的解决方案,我们为什么还要去拘泥自己构建的体系呢。

例子:

/** 原版 */
if (deletePage(page) == E_OK) {
    if (registry.deleteReference(page.name) == E_OK) {
        if (configKeys.deleteKey(page.name.makeKey()) == E_OK) {
            logger.log("page deleted");
        } else {
            logger.log("configKey not deleted");
        }
    } else {
        logger.log("deleteReference from registry failed");
    }
} else {
    logger.log("delete failed");
    return E_ERROR;
}

/** 改进版 */
try {
    deletePage(page);
    registry.deleteReference(page.name);
    configKeys.deleteKey(page.name.makeKey());
} catch (Exception e) {
    logger.log(e.getMessage());
}


/** 最佳方案 */
public void delete(Page page) {
    try {
        deletePageAndAllReferences(page);
    } catch (Exception e) {
        logError(e);
    }
}
private void deletePageAndAllReferences(Page page) throws Exception {
    deletePage(page);
    registry.deleteReference(page.name);
    configKeys.deleteKey(page.name.makeKey());
}
private void logError(Exception e) {
    logger.log(e.getMessage());
}

上述最佳方案,将异常处理与业务逻辑进行了分离,这是因为错误处理就是一件事,函数遵循单一职责应该将错误处理单独封装为一个函数,可以想象一下,如果业务逻辑模块长达数十行,在 try/catch 块中还如何保持良好的阅读体验。

TODO

标签:count,读后感,String,代码,private,之道,整洁,page,函数
来源: https://www.cnblogs.com/yzxmm/p/14710675.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有