我决定编写这本书的简编,众所周知,这本书的作者称之为“纯代码教师学校”。 马丁的目光似乎在说:
“我通过你看到了。 您是否不再遵循干净代码的原则?”

第1章干净代码
这几句话最干净的Martin版本代码是什么? 这是没有重复的代码,具有最少数量的实体,易于阅读,简单。 作为座右铭,人们可以选择:“首先是清晰度!”。
第2章有意义的名称
名称必须传达程序员的意图
变量,函数或类的名称应指示此变量存在的原因,作用和使用方式。 如果名称需要其他注释,则不能传达程序员的意图。 最好写出精确测量的内容和单位。
良好的变量名示例:daysSinceCreation;
目的:消除非显而易见性。
避免误传
请勿使用具有非故意含义的单词。 当心名称的细微差别。 例如,XYZControllerForEfficientHandlingOfStrings和XYZControllerForEfficientStorageOfStrings。
在变量名中使用小写字母“ L”和大写字母“ O”时,尤其是在组合使用时,会发现名称令人震惊的错误提示实例。 自然地,由于这些字母几乎分别与常数“ 1”和“ 0”没有区别而产生问题。
使用有意义的差异
如果名称不同,则它们应表示不同的概念。
形式(a1,a2,... aN)的“数字序列”与有意识的命名相反。 它们不携带信息,也不了解作者的意图。
没有信息的单词是多余的。 变量一词绝对不能出现在变量名中。 单词table绝不能出现在表名中。 为什么NameString比Name好? 名称可以是真实数字吗?
使用拼写名称:generationTimestamp比genymdhms好得多。
选择可搜索的名称
单字母名称只能在短方法中用于局部变量。
避免名称编码方案
通常,编码名称的发音不佳,而且很容易在其中打错字。
接口和实现
我(本书的作者)更喜欢保留接口名称不带前缀。 在旧代码中很常见的前缀I最多会分散注意力,最坏的情况下会传输不必要的信息。 我不会告诉用户他们正在使用接口。
类名
类和对象名称必须是名词及其组合:Customer,WikiPage,Account和AddressParser。 避免在类名中使用诸如Manager,Processor,Data或Info之类的词。 类名不能是动词。
方法名称
方法名称是动词或口头短语:postPayment,deletePage,save等。读/写方法和谓词由值和前缀get,set组成,并符合javabean标准。
避免双关语
作者的任务是使他的代码尽可能清晰。 无需仔细研究,一眼就可以看出该代码。 关注大众文学的模式,作者本人必须自由表达自己的思想。
添加有意义的上下文。
可以使用前缀addrFirstName,addrLastName,addrState等添加上下文。至少代码阅读器会理解变量是较大结构的一部分。 当然,创建一个名为Address的类会更正确,这样,即使编译器也知道变量是更多内容的一部分。
上下文不清楚的变量:
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 = ~_~quot quot~_~; 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); }
该函数有点长,并且始终使用变量。 要将函数划分为较小的语义片段,应创建GuessStatisticsMessage类,并使三个变量成为该类的字段。 这样,我们将为这三个变量提供一个明显的上下文-现在绝对显而易见的是,这些变量是GuessStatisticsMessage的一部分。
具有上下文的变量:
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 = ~_~quot quot~_~; verb = "is"; pluralModifier = ""; } private void thereAreNoLetters() { number = "no"; verb = "are"; pluralModifier = "s"; } }
不添加冗余上下文
短名称通常比长名称更好,只要代码阅读器只知道它们的含义即可。 名称中不要包含多余的上下文。
第三章功能
紧凑!
第一条规则:功能应该紧凑。
第二条规则:功能应该更加紧凑。
我的实践经验告诉我(以许多反复试验为代价)功能应该很小。 希望函数的长度不超过20行。
一项操作的规则
一个功能只能执行一个操作。 她必须表现出色。 她不应做任何其他事情。 如果一个函数仅执行该函数声明的名称下处于相同级别的那些动作,则该函数将执行一个操作。
功能部分
仅执行一项操作的功能无法有意义地划分为多个部分。
每个函数一个抽象级别
为了确保该功能仅执行“一项操作”,有必要验证该功能的所有命令都处于相同的抽象级别。
在函数中混合抽象级别总是会造成混乱。
从上到下阅读代码:降级规则
代码应该看起来像一个故事-从上到下。
每个功能后均应具有下一个抽象级别的功能。 这使您可以读取代码,依次读取抽象级别,同时读取函数列表。 我称这种方法为“降级规则”。
切换指令
编写紧凑的switch命令非常困难。 即使只有两个条件的switch命令占用的空间也比我认为单个块或函数所占用的空间大。 创建执行一件事的switch命令也很困难-本质上,switch命令始终执行N次操作。 不幸的是,切换命令并不总是被放弃,但是至少我们可以确保这些命令被隐藏在低级类中并且不会在代码中重复。 当然,多态可以帮助我们。
该示例仅显示一项操作,具体取决于员工的类型。
public Money calculatePay(Employee e) throws InvalidEmployeeType { switch (e.type) { case COMMISSIONED: return calculateCommissionedPay(e); case HOURLY: return calculateHourlyPay(e); case SALARIED: return calculateSalariedPay(e); default: throw new InvalidEmployeeType(e.type); } }
此功能有几个缺点。 首先,它很棒,随着新型工人的加入,它将会增长。 其次,它显然执行多个操作。 第三,它违反了单一责任原则,因为它有多种可能的变更原因。
第四,它违反了开放封闭原则,因为每次添加新类型时,功能代码都必须更改。
但是,最严重的缺点可能是该程序可能包含不限数量的具有相似结构的其他功能,例如:
isPayday(员工e,日期)
或
deliveryPay(员工e,货币付款)
等等。
所有这些功能将具有相同的缺陷结构。 解决此问题的方法是将switch命令埋入抽象工厂的基础中,而不显示给任何人。 工厂使用switch命令创建Employee的后代的适当实例,并调用函数calculatePay,isPayDay,deliverPay等,通过Employee接口传递多态传输。
public abstract class Employee { public abstract boolean isPayday(); public abstract Money calculatePay(); public abstract void deliverPay(Money pay); } ----------------- public interface EmployeeFactory { public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType; } ----------------- public class EmployeeFactoryImpl implements EmployeeFactory { public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType { switch (r.type) { case COMMISSIONED: return new CommissionedEmployee(r) ; case HOURLY: return new HourlyEmployee(r); case SALARIED: return new SalariedEmploye(r); default: throw new InvalidEmployeeType(r.type); } } }
关于切换命令的一般规则是,这些命令如果在程序中出现一次,则有效,用于创建多态对象,并隐藏在继承关系后面,以使系统其余部分看不见。 当然,没有没有例外的规则,在某些情况下有必要违反该规则的一个或多个条件。
使用有意义的名称
实施此原理的努力的一半归功于为执行单个操作的紧凑功能选择好名称。 函数越小越专业,就越容易为其选择一个有意义的名称。
不要害怕使用长名称,长而有意义的名称要好于简短而晦涩的名称。 选择一种方案,使其易于读取函数名称中的单词,然后从这些单词中命名一个描述函数目的的名称。
功能参数
在理想情况下,函数参数的数量为零。 以下是具有一个参数(一元)和两个参数(二元)的函数。 应尽可能避免使用带有三个参数(三进制)的函数。
输出自变量比输入自变量更容易使情况混乱。 通常,没有人期望函数返回参数中的信息。 如果您没有参数就无法管理,请尝试至少将自己限制为一个输入参数。
使用输出参数而不是返回值的转换会使读者感到困惑。 如果函数转换其输入参数,则结果
必须在返回值中传递。
标志参数
自变量的参数很丑陋。 传递函数的逻辑含义是一种非常可怕的习惯。 它立即使该方法的签名复杂化,大声宣称该函数执行多个操作。 如果该标志为true,则执行一个操作,如果为false,则执行另一操作。
二元函数
具有两个参数的函数比一元函数更难理解。 当然,在某些情况下,两个参数的形式是合适的。 例如,调用Point p = new Point(0,0); 绝对合理。 但是,在我们的案例中,两个参数是具有相同值的有序组件。
对象作为参数
如果一个函数应接收两个或三个以上的参数,则其中的某些参数很有可能应打包在一个单独的类中。 请考虑以下两个声明:
Circle makeCircle(double x, double y, double radius); Circle makeCircle(Point center, double radius);
如果将变量作为一个整体进行传递(如本例中的x和y变量),则很可能它们一起形成了一个应得其名的概念。
动词和关键词
为函数选择一个好名字可以在很大程度上解释该函数的含义以及其参数的顺序和含义。 在一元函数中,函数本身及其参数必须形成自然的动词/名词对。 例如,形式为write(名称)的调用看起来非常有用。
读者理解,无论“名称”是什么,它都在“书面”位置。 更好的是writeField(名称)记录,该记录报告“名称”已写入某些结构的“字段”。
最后一个条目是在函数名称中使用关键字的示例。 以这种形式,参数名称编码在函数名称中。 例如,assertEquals可以写为assertExpectedEqualsActual(预期的,实际的)。 这在很大程度上解决了记住参数顺序的问题。
命令和请求的分离
函数必须执行某些操作或回答某些问题,但不能同时执行。 函数要么更改对象的状态,要么返回有关该对象的信息。 合并两个操作通常会造成混乱。
隔离尝试/捕获块
try / catch块看起来很丑。 它们混淆了代码的结构,并将错误处理与常规处理混合在一起。 因此,建议将try和catch块的主体分成单独的功能。
错误处理为一项操作
功能必须执行一项操作。 错误处理是一项操作。 这意味着处理错误的功能不应执行其他任何操作。 因此,如果在函数中存在try关键字,则它必须是函数中的第一个单词,并且在catch / finally块之后应该没有其他内容。
第三章到此结束。