[关闭]
@DevWiki 2015-07-02T21:11:12.000000Z 字数 3790 阅读 1103

CleanCode笔记---函数

CleanCode


更多内容详见:CleanCode笔记

函数要短小

函数第一原则要短小,第二原则还是要短小

如果函数需要翻页,就有拆分的必要了.翻页往往会导致看着下面忘了上面.

一个函数不要太长,一般每行不超过100个字符,行数不超过30行.

if, else, while, case语句中代码应只有一行,该行要描述特定条件下要做的事情.

只做一件事

函数也应该遵守单一职责原则,即一个函数制作一件事情.比如一个检查登陆账户的函数:

  1. public boolean checkAccount() {
  2. boolean isAccountAccess = false;
  3. if(userName.isAccess() && password.isAcces()){
  4. isAccountAccess = true;
  5. login();
  6. } else {
  7. isAccountAccess = false;
  8. }
  9. }

函数名称为checkAccount,意为检查账户;而里面当账户可用是却请求登录.

如果开发人员未仔细检查代码,只看函数名,很容易出现问题.检查账户的函数就应该只检查账户,而不应该在做其他事情.

每个函数一个抽象层次

一个函数不应该既有抽象又有细节!

我们经常写Android的Activity时这样:

  1. protected void onCreate(Bundle savedInstanceState) {
  2. super.onCreate(savedInstanceState);
  3. setContentView(R.layout.activity_main);
  4. ToastUtil.init(this);
  5. startButton = findViewById(R.id.start_button);
  6. resultView= findViewById(R.id.result_view);
  7. startButton.setOnClickListener(this);
  8. resultView.setText("结果为:");
  9. }

抛去2,3两行不说.第5行为抽象,而第7~10行为细节,这样读下来,突然发生了变化,会误入到细节之中.
不如改为下面这样:

  1. protected void onCreate(Bundle savedInstanceState) {
  2. super.onCreate(savedInstanceState);
  3. setContentView(R.layout.activity_main);
  4. ToastUtil.init(this);
  5. initView();
  6. }
  7. private void initView(){
  8. startButton = findViewById(R.id.start_button);
  9. resultView= findViewById(R.id.result_view);
  10. startButton.setOnClickListener(this);
  11. resultView.setText("结果为:");
  12. }

这样只需阅读在onCreate中做了哪些事情,而不需要去知道具体怎么做的.所以一个函数应保持同一抽象层次.

switch语句

我们一般写switch语句往往如下:

  1. switch (type) {
  2. case 0:
  3. //
  4. break;
  5. case 1:
  6. //
  7. break;
  8. case 2:
  9. //
  10. break;
  11. default:
  12. break;
  13. }

当有多个分支时,switch语句往往会很长.可以通过以下来改善:
1. 每个case下只有一行代码.
2. 使用策略模式分解.

使用描述性的名称

函数的名称应该看到名字就知道该函数要做什么事情
比如上面的checkAccount(),看到改名称就应该知道该函数做的事情是检查账户,其返回值为boolean类型,则返回值应该是账户合法或者不合法.

函数参数

关于函数的参数个数:

零个最佳,其次是一参,再次是二参,尽量避免三参或更过

零个最佳

最理想的函数是没有参数,直接调用,不考虑其需要的条件.因为函数自己已经拥有相应的条件.
比如Android中的Activity的onStart(), onResume()等

其次是一参

一个参数是最普遍的形式.
比如常用的setter和getter方法.一个参数可以明确函数运行必要的条件.

再次是二参

两个参数已经可以出现混淆了.
比如String类有个方法replace(String oldChar, String newChar)如果不是参数的名称,很容易混淆是用谁代替谁.

所以当函数出现两个参数时,务必给参数取个好的名称,以区分参数的意义.

举个返利,放我们给一个对象集合排序时,会实现Comparator接口,如下:

  1. class MyCompar implements Comparator<String>{
  2. @Override
  3. public int compare(String lhs, String rhs) {
  4. // TODO Auto-generated method stub
  5. return 0;
  6. }
  7. }

你知道lhs与rhs的含义么?他们两个比较的结果是什么意思?

避免三参或更多

三个参数更容易出现问题.特别是三个参数类型一样时,往往会顺序错误.
比如求梯形的面积,我们都知道梯形面积S = (上底a + 下底b) * 高h /2;

  1. public float calculateArea(int a, int b, int c){
  2. return (a + b)*c/2;
  3. }

如果不看细节,你能一下子就看出参数的含义么?

有需要三个或者更多参数时就应该抽象为一个类了.
上面的方法可以改为:

  1. public float calculateArea(Trapezoid trapezoid){
  2. return (trapezoid.getTopWidth() + trapezoid.getBottomWidth()) * trapezoid.getHeight / 2;
  3. }

不标志参数(boolean值)

不要使用boolean类型的参数!!!
传入boolean类型的参数,那么该函数必定违反单一职责原则!
为何不拆分为两个函数呢?

无副作用

上面有个checkAccount()函数其实就是一个有副作用的函数!

方法名为checkAccount(),且函数值也是boolean类型的.但是它却在账户可用时做了登录操作!!!

如果不知道该函数的细节,多次调用会导致重复登陆!!!

所以函数内容一定要和函数名称相同.

分割指令与查询

函数要么做什么事,要么回答什么事情,但二者不可兼得.

函数应该修改某对象的状态或者返回该对象的有关信息.

例子:

  1. public boolean set(String attribute, String value);

该函数用于修改属性,如果成功返回true,如果不存在那个属性则返回false.这样会导致:

  1. if(set("username", "jack"))..

这句是什么意思呢?意思是设置username为jack成功呢?还是设置username之前检查username是否为jack呢?

所以要分割指令和查询:

  1. public void setUserName(String name);
  2. public boolean isAttrExists(String attr);

使用异常代替返回错误码

我们经常这样写:

  1. public int deletePage(Page page);
  2. if(deletePage(page) == E_OK)...

但是这样会导致深层次的嵌套:

  1. if(deletePage() == E_OK){
  2. if(registy.deleteReference(page.name) == E_OK){
  3. if(configKeys.deleteKey(page.name.makeKey()) == E_OK) {
  4. logger.log("page deleted");
  5. } else {
  6. logger.log("configkey not delete");
  7. }
  8. } else {
  9. logger.log(delteReference from registry failed);
  10. }
  11. } else {
  12. logger.log("delete failed");
  13. return E_ERROR;
  14. }

如果改为返回异常,错误处理就能从主路径中分离出来:

  1. try{
  2. deletePage(page);
  3. registry.deleteReference(page.name);
  4. configKeys.deleteKey(page.name.makeKey());
  5. }

抽离Try/Catch代码块

代码处理直接放在try..catch中,要抽离出来,另外形成函数:

  1. public void deletePage(){
  2. try{
  3. deletePageAndReferences(page);
  4. } catch (Exception e){
  5. logError(e);
  6. }
  7. }

处理错误就是一件事

一个函数只做一件事,处理错误就是一件事,所以错误处理要抽离单独的函数.

使用异常代替错误码,新的异常可以从异常类中派生,无需重新编译或重新部署.

别重复自己

代码混乱最直接的原因就是代码重复.重复可能是软件中一切邪恶的根源.所以别重复自己!!!

结构化编程

结构化编程规范:

每个函数,函数中的每个代码块应该只有一个入口,一个出口.

函数应遵守此规范,但是对于小函数,这些规则助益不大,只有在大函数中才有明显的好处.

只要函数短小,偶尔出现的return, break或 continue语句就没坏处,甚至比单入单出原则更有表达力.

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注