Skip to content

第十二章 把想法变成代码

当把一件复杂的事情向别人解释时,那些小细节很容易就会让它们迷惑。把一个想法用“自然语言”解释是个很有价值的能力,因为这样其他知识没有你这么渊博的人才可以理解他。这需要把一个想法精炼成最重要的概念。这样不仅帮助他人帮助,而且也帮助你自己把这个想法想的更清晰。

清楚地描述逻辑

下面时来自一个网页的一段 PHP 代码,这段代码在一段安全代码的顶部。他检查是否授权用户看到这个页面,如果没有,马上返回要一个页面告诉他没有授权。

php
$is_admin = is_admin_request();

if ($document) {
  if (!$is_admin && ($document['username'] != $_SESSION['username'])) {
    return not_authorized();
  }
} else {
  if (!$is_admin){
    return not_authorized();
  }
}

让我们用自然语言逻辑来描述这个逻辑

授权你有两种方式

  1. 你是管理员
  2. 你不是管理员,但你拥有当前文档 否则,无法授权你

基于这个描述,我们可以编写出

php
$is_admin = is_admin_request();

if ($is_admin) {
  // authorized
} else if ($document && ($document['username'] == $_SESSION['username'])) {
  // authorized
} else {
  return not_authorized();
}

把这个方法应用于更大的问题

假设我们能由一个记录股票采购的系统。每笔交易都有 4 块数据:

  • time 一个精确的购买时间
  • ticker_symbol 公司简称,如:GooGle
  • price 价格,如:$600
  • number_of_shares 股票数量,如:100

由于一些奇怪的原因,这些数据分布在三个数据库表中,如下所示,在每个数据库中,time 是唯一的主键

timeticker_symbol-timeprice-timenumber_of_shares
3:45IBM3:45$1203:4550
3:59IBM4:30$6003:59200
4:30GOOGLE5:00$254:1075
5:20AAPL5:20$2004:30100
6:00MSFT6:00$255:2080

现在我们要写一个程序来吧三个表联合在一起(就像在 SQL 中的 JOIN 操作所做的那样)。这个步骤应该是简单的,因为这些行都是按 time 来排序的,但是有些行是缺失的。我们希望找到这 3 个 time 匹配的所有行,忽略任何不匹配的行。

下面是一段 Python 代码,用来找到所有的匹配行:

py
def PrintStockTransactions(): 
  stock_iter = db_read("SELECT time, ticker_symbol From ...")
  price_iter = db_read("SELECT time, price From ...")
  num_shares_iter = db_read("SELECT time, number_of_shares From ...")

  while stock_iter and price_iter and num_shares_iter:
      stock_time = stock_iter.time
      price_time = price_iter.time
      num_shares_time = num_shares_iter.time

      if stock_time != price_time or stock_time != num_shares_time:
          if stock_time <= price_time and stock_time <= num_shares_time:
              stock_inter.NextRow()
            elif price_time <= stock_time and price_time <= num_shares_time:
              price_iter.NextRow()
            elif num_shares_time <= stock_time and num_shares_time <= price_time:
              num_shares_iter.NextRow()
            else:
              assert False
            continue
      
      assert stock_time == price_time == num_shares_time

      print "@", stock_time,
      print stock_iter.ticker_symbol,
      print price_iter.price,
      print num_shares_iter.number_of_shares

      stock_iter.NextRow()
      price_iter.NextRow()
      num_shares_iter.NextRow()

再一次,让我们退一步来用自然语言描述我们要做的事情:

  • 我们并行的读取三个行迭代器。
  • 只要这些行不匹配,向前找,直到它们匹配。
  • 然后输出匹配的行,在继续向前。
  • 一直做到没有匹配的行。

这里最乱的部分就是处理“向前找,直到它们匹配”的代码,我们将其抽离到一个名叫 AdvanceToMatchingTime() 的新函数中。

并且我们重新描述一下:

  • 看一下每个当前行,如果它们匹配,那么就完成了
  • 否则,向前移动任何“落后的行”。
  • 一直这样做直到所有行匹配

这个描述清晰的多,并且比以前的代码更优雅。一件值得注意的是描述从未提起其他解决问题的细节。这意味着我们可以同时把变量重命名得更简单,更通用。

py
def AdvanceToMatchingTime(row_iter1, row_iter2, row_iter3) {
  while row_iter1 and row_iter2 and row_iter3:
    t1 = row_iter1.time
    t2 = row_iter2.time
    t3 = row_iter3.time

    if t1 == t2 == t3:
        return t1
    tmax = max(t1, t2, t3)

    if t1 < tmax: row_iter1.NextRow()
    if t2 < tmax: row_iter2.NextRow()
    if t3 < tmax: row_iter3.NextRow()

    return None
}
py
def PrintStockTransactions(): 
  stock_iter = db_read("SELECT time, ticker_symbol From ...")
  price_iter = db_read("SELECT time, price From ...")
  num_shares_iter = db_read("SELECT time, number_of_shares From ...")

  while True:
      time = AdvanceToMatchingTime(stock_iter, price_iter, num_shares_iter)
      if time is None
        return

      print "@", stock_time,
      print stock_iter.ticker_symbol,
      print price_iter.price,
      print num_shares_iter.number_of_shares

      stock_iter.NextRow()
      price_iter.NextRow()
      num_shares_iter.NextRow()

如你所见,这段代码容易理解的多。

总结

本章讨论了一个简单的技巧,用自然语言描述程序然后用这个描述来帮助你写出更自然的代码。这个技巧出人意料的简单,但很强大。