第十二章 把想法变成代码
当把一件复杂的事情向别人解释时,那些小细节很容易就会让它们迷惑。把一个想法用“自然语言”解释是个很有价值的能力,因为这样其他知识没有你这么渊博的人才可以理解他。这需要把一个想法精炼成最重要的概念。这样不仅帮助他人帮助,而且也帮助你自己把这个想法想的更清晰。
清楚地描述逻辑
下面时来自一个网页的一段 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();
}
}
让我们用自然语言逻辑来描述这个逻辑
授权你有两种方式
- 你是管理员
- 你不是管理员,但你拥有当前文档 否则,无法授权你
基于这个描述,我们可以编写出
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 是唯一的主键
time | ticker_symbol | - | time | price | - | time | number_of_shares |
---|---|---|---|---|---|---|---|
3:45 | IBM | 3:45 | $120 | 3:45 | 50 | ||
3:59 | IBM | 4:30 | $600 | 3:59 | 200 | ||
4:30 | 5:00 | $25 | 4:10 | 75 | |||
5:20 | AAPL | 5:20 | $200 | 4:30 | 100 | ||
6:00 | MSFT | 6:00 | $25 | 5:20 | 80 |
现在我们要写一个程序来吧三个表联合在一起(就像在 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()
如你所见,这段代码容易理解的多。
总结
本章讨论了一个简单的技巧,用自然语言描述程序然后用这个描述来帮助你写出更自然的代码。这个技巧出人意料的简单,但很强大。