程序员可以分为三个层次:普通程序员、工程师 、架构师
有意义的命名
1、名副其实
(1)变量、函数或者类的名称应该已经答复了所有问题,应当说明了做什么事,应该怎么用
(2)如果名称需要注释来补充,就不算名副其实。
(3)以日期为例:应当指明计量对象和计量单位
int d ------> int daysSinceCreation / int daysSinceModification / int fileAgeInDays
2、避免误导
(1)别用accountList来指一组账号,除非真的是List类型
accountList -----> accountGroup / bunchOfAccounts
(2)提防使用不同之处较小的名称,即外形非常相似,只有微小处的区别
XYZControllerForEfficientHandlingOfStrings ----- XYZControllerForEfficientStorageOfStrings
(3)不要以 l 和 o 做变量名,难以和 1 , 0 区分
3、做有意的区分
(1)添加数字或废话(info、data)是错误的
int a1, int a2 -----> int source , int destination
accountData 与 account没有区别 , theMessage 与 message 没有区别
(2)Variable一词永远不应当出现在变量名中,Table一词永远不应当出现在表名中
4、使用读得出来的名称
(1)切记不要傻乎乎自造词,而使用英语词
generationTimestamp modificationTimestamp recordId
5、使用可搜索的名称
(1)名称长短应该与其作用域的大小相对应,若变量或者常量可能在代码中多处使用,应该赋予其便于搜索的名称。
6、避免使用编码
(1)不要把类型或作用域以前缀或后缀方式编进名称
IShapeFactory , ShapeFactory ------> ShapeFactroy , ShapeFactoryImpl
7、避免思维映射
(1)在多数情况下,单字母名称不是个好选择
8、类名
(1)类名和对象名应该是名词或者名词短语
Customer、WikiPage、Account这些名词,避免使用Manager、Processor、Data或者Info这样的类名。类名不应当是动词
9、方法名
(1)方法名应该是动词或者动词短语
postPayment、deletePage或者save
(2)属性访问器、修改器和断言应该根据其值命名,依据Javabean标准加上get、set、is前缀
10、每个概念对应一个词
(1)给每个抽象概念选一个词,并且一以贯之
使用fetch、retrieve和get来给在多个类中的同种方法命名。
11、别用双关语
(1)遵循“一词一义”,避免将一个单词用于不同的目的
(2)代码作者应尽力写出易于理解的代码
12、词汇领域
(1)尽量用计算机领域词汇命名,如果不能,用所涉领域名称命名
17、不要添加没用的语境
(1)只要短名足够清楚,就要比长名称好
例如需要将邮件地址、MAC地址和web地址相区别:PostalAddress、MAC 和 URL
函数
1、短小
(1)函数的第一规则是要短小,函数20行封顶最佳;
(2)每个函数都只做一件事,每个函数依序把你带到下一个函数;
2、只做一件事
(1)函数中的语句都要在同一抽象层级;
(2)让代码拥有自顶向下的阅读顺序;
3、switch语句
(1)switch语句违反了单一职责原则(Single Responsibility Principle)、开放封闭原则(Open Closed Principle),可以用工厂模式进行重构。
- 把case中的角色抽象成继承(实现)了统一抽象类(接口)的子类;
- 子类各自实现各自的抽象方法;
- 工厂进行定制,按需创建子类的对象
public class BeforeRefraction {
enum EmployeeType{
ENGINEER,SALESMAN,MANAGER;
}
public static int m_basicSalary;
public static int m_commission;
/**
* switch语句接收枚举类型,判断输出相应的薪资算法
* @param empType Employee type in enum
* @return Payment of each type
*/
public int PayAmount(EmployeeType empType) throws Exception{
switch (empType){
case ENGINEER:
return m_basicSalary;
case SALESMAN:
return m_basicSalary + m_commission;
case MANAGER:
return 2 * m_basicSalary;
default:
throw new Exception("no such employee type!");
}
}
/**
* switch再来一遍,接收枚举类型,判断输出相应的职位描述
* @param empType Employee type in enum
* @return Description of each type
*/
public String GetDescription(EmployeeType empType) throws Exception{
switch (empType){
case ENGINEER:
return "Coding, Debug, Optimization";
case SALESMAN:
return "Getting contracts";
case MANAGER:
return "Analysis, Scheduling, Reporting";
default:
throw new Exception("no such employee type!");
}
}
}
//----------------------------------------------------------------------------------
// Step1:把Switch语句中的每个case抽取出来:
// class Engineer
// class Salesman
// class Manager
// Step2:把它们共有的部分抽象为一个抽象类或者接口: abstract class Employee
// Step3:工厂接收抽象类或者接口的引用变量,再根据引用变量的类型按条件new出具体的子类。工厂可以是一个单独的类,也可以是放在上面的抽象类中
enum EmployeeType{
ENGINEER,SALESMAN,MANAGER;
}
abstract class Employee{
public static int m_basicSalary;
public static int m_commission;
/*
* 工厂方法也可以放在这里,仍然是一个静态方法
*/
public abstract String getDescription();
public abstract int PayAmount();
}
//把Switch语句中的每个case抽取出来,然后把他们共有的部分抽象为一个抽象类或者
class Engineer extends Employee{
@Override
public String getDescription() {
return "Coding, Debug, Optimization";
}
@Override
public int PayAmount() {
return m_basicSalary;
}
}
class Salesman extends Employee{
@Override
public String getDescription() {
return "Getting contracts";
}
@Override
public int PayAmount() {
return m_basicSalary + m_commission;
}
}
class Manager extends Employee{
@Override
public String getDescription() {
return "Analysis, Scheduling, Reporting";
}
@Override
public int PayAmount() {
return m_basicSalary *2;
}
}
/**
* 工厂类,这里只有一个静态方法
* 工厂类只做一件事:按条件new对象
* 这样一来,我们在main方法中就不用new不同的employee对象了
*/
class Factory{
public static Employee getEmployee(EmployeeType employee) throws Exception{
switch(employee){
case ENGINEER:
return new Engineer();
case SALESMAN:
return new Salesman();
case MANAGER:
return new Manager();
default:
throw new Exception("no such employee type!");
}
}
}
public class AfterRefraction {
public static void main(String[] args) {
Employee employee = null;
try {
employee = Factory.getEmployee(EmployeeType.ENGINEER);
} catch (Exception e) {
e.printStackTrace();
}
employee.getDescription();
System.out.println("Salary:"+employee.PayAmount());
}
}
//注:上述代码案例取自于Sarah_mq博客(原文:https://blog.csdn.net/weixin_35813749/article/details/53837878)
4、使用描述性的名称
(1)函数越短小、功能越集中,就越便于取个好名字;
(2)长而具有描述性的名称,要比短而令人费解的名称好,尽量名称能说清其功用;
(3)命名方式要保持一致,使用与模块名一脉相承的短语、名词和动词给函数命名。
includeSetupAndTeardownPages , includeSetupPages , includeSuiteSetupPage , includeSetupPage等
5、函数参数
(1)最理想的参数数量是0,其次是1,再次是2,应该尽量避免3;
(2)减少参数数量,便于覆盖所有可能进行测试
(3)如果函数要对输入参数进行转换操作,转换结果就该体现为返回值;
StringBuffer transform(StringBuffer in) 要好于 void transform(StringBuffer in)
(4)标识参数:向函数传入布尔值是不好的做法,意味着本函数不止做一件事。
(5)参数对象:如果函数需要2个、3个或3个以上参数,就说明其中一些参数应该封装为类了;
Circle makeCircle( double x , double y , double radius) ----> Circle makeCircle(Point center , double radius)
(6)参数列表:有可变参数的函可能是一元、二元甚至是三元,超过这个数量就可能要犯错了。
void monad(Integer ... args);
void dyad(String name , Integer ... args);
void triad(String name , int count , Integer ... args);
(7)动词与关键字
write(name) ----> writeField(name):告诉读者“name”是一个“field”
assertEqual -----> assertExpectedEqualsActual(expected , actual)
(8)分隔指令与询问:函数要么做什么事,要么回答什么事,但二者不可兼得
6、使用异常替代返回错误码,错误处理代码能从主路径代码中分离出来
(1)抽离Try/Catch代码块:另外形成函数处理正常流程
public void delete(Page page){
try{
deletePageAndAllReferences(page);
}catch(Exception e){
logEeeor(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());
}
//上述delete函数只与错误处理相关,很容易理解然后忽略掉;
//deletePageAndAllReference函数只与完全删除一个Page有关;
//错误处理可以忽略掉
(2)错误处理就是一件事:catch/finally代码块后面也不该有其他内容
(3)Error.java依赖磁铁:其他许多类都得导入和使用它,当Error枚举修改时,所有其他的类都需要重新编译和部署,对Error类的修改造成了负面的压力,而使用异常代替错误码,新异常就可以从异常类派生出来,无需重新编译或重新部署。
7、别重复自己
(1)代码因此而臃肿,且当算法改变时需要修改所有的重复代码块,重复是软件中一切邪恶的根源。
(2)打磨代码:分解函数、修改名称、消除重复
注释
1、注释不能美化糟糕的代码
(1)注释存在的越久,就离其所描述的代码越远,越来越变得全然错误,因为程序员不能坚持维护注释;
(2)写注释的常见动机之一是糟糕代码的存在;
2、用代码来阐述
(1)用代码解释大部分的意图
3、好注释
(1)唯一真正好的注释是你想办法不去写的注释
(2)版权及著作权声明
(3)注释吧某些晦涩难明的参数或返回值的意义翻译为某种可读形式
(4)用于警告其他程序员会出现某种后果的注释也是有用的
// SimpleDateFormate is not thread safe, so we need to create each instance independently
SimpleDateFormat df = new SimpleDateFormat("EEE, dd MMM HH:mm:ss z");
(5)TODO注释,放置要做的工作列表
(6)注释可以用来放大某种看来不合理之物的重要性
(7)如果编写公共API,就该为它编写良好的Javadoc
4、坏注释
(1)不要对显然之事喋喋不休,毫无新意
private void startSending(){
try{
doSending();
}catch(SocketException e){
// normal:someone stopped the request
}catch(Exception e){
try{
response.add(ErrorResponder.makeExceptionString(e));
response.closeAll();
}catch(Exception e){
// Give me a break
}
}
}
//对于最末一个try/catch代码块拆解到单独的函数中
private void startSending(){
try{
doSending();
}catch(SocketException e){
// normal:someone stopped the request
}catch(Exception e){
addExceptionAndCloseResponse(e);
}
}
private void addExceptionAndCloseResponse(Exception e){
try{
response.add(ErrorResponder.makeExceptionString(e));
response.closeAll();
}catch(Exception e){
}
}
(2)直接把代码注释掉是一种令人讨厌的做法
(3)如果一定要写注释,请确保它描述了离它最近的代码,别在本地注释的上下文环境中给出系统级的信息。
(4)信息过多:别在注释中添加有趣的历史性话题或者无关的细节描述
(5)短函数不需要太多描述,只需要为短函数选个好名字
------------------------------------------------------------------ end ---------------------------------------------------------------------------------------------------