Skip to content

第十一章 一次只做一件事

同时在做几件事的代码很难理解。一个代码可能初始化对象,清除数据,解析输入,然后应用业务逻辑,所有这些都同时进行。如果所有这些代码都纠缠在一起,对于每个任务都很难靠其自身来帮助你理解它从哪里开始,到哪里结束。

::: 关键思想 应该把代码组织得一次只做一件事情 :::

你也许听过这个建议:“一个函数应当只做一件事”。我们的建议和这差不多,但不是关于函数边界的。当然,把一个大函数拆分成多个小一些的函数是好些的。但是就算你不这样做,你仍然可以在函数内部组织代码,使得它感觉像是有分开的逻辑段。

任务可以很小

假设又一个博客上的投票插件,用户可以给一个评论投“上”或者“下”,每条评论的总分为所有投票之后:上对应 +1,下对应 -1。

下面这个函数计算总分,并且对 old_vote 和 new_vote 的各种组合有效:

js
var vote_changed = function (old_vote, new_vote) {
  var score = get_score();
  if (new_vote !=== old_vote) {
    if (new_vote === 'Up') {
      score += (old_vote === 'Down' ? 2 : 1);
    } else if (new_vote === 'Down') {
      score -= (old_vote === 'Up' ? 2:  1);
    } else if (new_vote === '') {
      score += (old_vote === 'Up' ? -1 : 1);
    }
  }
  set_score(score)
}

任务1: 把投票解析成数字

js
var vote_value = function (vote){
  if (vote === 'Up') {
    return +1;
  }
  if (vote === 'Down') {
    return -1;
  }
  return 0;
}

任务2:更新分数

js
var vote_changed = function (old_vote, new_vote) {
  var score = get_score();
  score -= vote_value(old_vote);
  score += vote_value(new_vote);
  set_score(score)
}

更大型的例子

我们做过一个网页爬虫系统,会在下载每个网页后调用一个 UpdateCounts() 的函数来增加不同的统计数据:

java
void UpdateCounts(HttpDownload hd) {
  counts["exit State"][hd.exit_state()]++;
  counts["Http Response"][http.http_response()]++;
  counts["Content-Type"][hd.content_type()]++;
}

这是我们希望代码成为的样子,实际上 HttpDownload 对象上面没有上面所示的任何方法。相反,HttpDownload 是一个非常复杂的类,有非常嵌套类,并且得我们自己把它们挖出来。更糟糕得是,有时有些值不知道是什么,这种情况下我们只能用 “unknown” 作为默认值。

java
void UpdateCounts(HttpDownload hd) {
  if (!hd.has_event_log() || !hd.event_log().has_exit_state()) {
    count["Exit State"]["unknown"]++;
  } else {
    String state_str = ExitStateTypeName(hd.event_log().exit_state());
    counts["Exit State"][state_str]++;
  }
  if (!hd.has_http_headers()) {
    counts["Http Response"]["unknown"]++;
    counts["Content-Type"]["unknown"]++;
    return ;
  }

  HttpHeaders headers = hd.http_headers();

  if (!headers.has_response_code()) {
    counts["Http Response"]["unknown"]++;
  } else {
    String code = StringPrintf("%d", headers.response_code());
    counts["Http Response"][code]++;
  }

  if (!headers.has_content_type()) {
    counts["Content-Type"]["unknown"]++;
  } else {
    String content_type = ContentTypeMime(headers.content_type());
    counts["Content-Type"][content_type]++;
  }
}

我们可以通过把其中一些任务分割到代码中单独的区域来改进这段代码

java
void UpdateCounts(HttpDownload hd) {
  String exit_state = "unknown";
  String http_response_code = "unknown";
  String content_type = "unknown";

  if (hd.has_event_log() && hd.event_log().has_exit_state()) {
    exit_state = ExitStateTypeName(hd.event_log().exit_state());
  }

  if (hd.has_http_headers() && hd.http_headers().has_response_code()) {
    http_response_code = StringPrintf("%d", headers.response_code());
  }

  if (hd.has_http_headers() && hd.http_headers().has_content_type()) {
    content_type = ContentTypeMime(headers.content_type());
  }

  counts["Exit State"][exit_state]++
  counts["Http Response"][http_response_code]++;;
  counts["Content-Type"][content_type]++;
}

当然,我们可以用另一种方法来改进这段代码,通过引入3个辅助函数:

java
String ExitState(HttpDownload hd) {
  if (hd.has_event_log() && hd.event_log().has_exit_state()) {
    return  ExitStateTypeName(hd.event_log().exit_state());
  }
  return "unknown"
}

String HttpResponseCode(HttpDownload hd) {
  if (hd.has_http_headers() && hd.http_headers().has_response_code()) {
    return StringPrintf("%d", headers.response_code());
  }
  return "unknown"
}

String ContentType(HttpDownload hd) {
  if (hd.has_http_headers() && hd.http_headers().has_content_type()) {
    return ContentTypeMime(headers.content_type());
  }
  return "unknown"
}

void UpdateCounts(HttpDownload hd){
  counts["Exit State"][ExitState(hd)]++
  counts["Http Response"][HttpResponseCode(hd)]++;;
  counts["Content-Type"][ContentType(hd)]++;
}

总结

本章给出了一个组织代码的技巧:一次只做一件事情。