如果编程就是写作

很多人会把编程比作写作,比如《Thinking in Java》的作者 Bruce Eckel 就说:“程序员就是作家。”二者的确也有许多相似之处,好文章和好代码,都离不精确的结构与优美的表达。写作时,每个人都能用词语拼出句子,组成文章,但并非所有人都能写出好文章,编程也是一样。

不过严格来说,编程和写作虽有少许相似,却仍是两个迥异的技能,在很多方面差别很大。比如我们很少会重写一篇已发表的文章,但重构一段能正常运行的代码实属家常便饭。

不过这一次,我们不妨把编程中那些复杂概念抛掷一旁,来一次奇思妙想:“如果编程就是写作,我们能从中学到些什么?”

1. 使用人称代词

写文章时,我们常常会使用人称代词来指代人物的名字,以此减少冗余,提升文本的流畅度。这个技巧当然也可以用到编程中。

以下代码重复使用了一个冗长的表达式:

def run_server():
    if check(data.servier_config.host):
        start_server(data.servier_config.host)
    else:
        logger.error('Server %s is invalid.', data.servier_config.host)

定义一个临时变量,作为代词使用来简化代码:

def run_server():
    host = data.servier_config.host
    if check(host):
        start_server(host)
    else:
        logger.error('Server %s is invalid.', host)

2. 搭配使用长短句

写作时, 如何选择句子的长短是一门学问。汪曾祺说过:“语言的奥秘,说穿了不过是长句与短句的搭配。”长句信息量大,但太多长句会加重读者的阅读负担。短句节奏感强,但全是短句的文章也会给人一种琐碎感。

通常来说,短句为主,长句为辅是一种可读性较好的的风格。

对于代码来说,每行语句里的信息量同样会因为表达式的长短产生分别。写代码时,我们不应该过于“着急”,把太多函数调用、字面量和运算符挤在同一行语句里。也不要过于“散漫”,将逻辑切分成了太多短小的语句碎片。

下面是一份过于追求“长句”的代码:

results = [
    task.result if task.result_version == VERSION_2 else get_legacy_result(task)
    for tasks_group in tasks
    for task in in tasks_group
    if task.is_active() and task.has_completed()
]

拆解为短句,代码会更好读:

results = []
for tasks_group in tasks:
    for task in tasks_group:
        if not (task.is_active() and task.has_completed()):
            continue

        if task.result_version == VERSION_2:
            result = task.result
        else:
            result = get_legacy_result(task)
        results.append(result)

在这个基础上,也可以尝试适当填入长句,代码的节奏感会随之改变:

results = []
task_objs = (t for t in group for group in tasks)
for task in task_objs:
    if not (task.is_active() and task.has_completed()):
        continue

    item = task.result if task.result_version == VERSION_2 else get_legacy_result(task)
    results.append(item)

3. 大段落与小段落

和句子类似,段落也有长短之分。除非在搞严肃的文学创作,否则我们绝不应该在文章中连续使用太多长段落。同分句的原则一样,小段落为主,大段落为辅是比较易读的风格。

写代码时,我们可以适当用空行来切分长篇累牍的代码,通过段落来区分代码逻辑的不同阶段,制造节奏感。除了制造字面意义上的“段落”,将长代码拆分为小函数也可达到不错的效果。

4. 统一叙事角度

不论写什么故事,一个恰当的叙事角度必不可少。比如在侦探小说中,选择用罪犯还是侦探的角度来讲故事,效果截然不同。通常来说,在一个完整的小说章节内,成熟的作者只会使用一个叙事角度,不会随意进行切换。

假如叙事角度换来换去,读上去会很奇怪。以下面这段文字为例:

小 R 抱着笔记本电脑坐在客厅的沙发上,打开了《海贼王》的最新一话,“这路飞到底当上海贼王了没有?”他一边看,一边自言自语。小 C 手拿一只黄色橡皮小鸭,从房间一摇一摆的走出来,“真无聊呀,找谁来陪我玩过家家好呢?”她在心里盘算着。

在这段文字中,作者先是站在 小 R 的角度描写其心理活动,之后又突然换成了小 C。这种切换,容易让读者感觉到混乱和不自然。

如果对应到编程里,叙事角度的切换同代码职责的变换非常相似。为了让代码读起来更流畅,一个函数最好只拥有一种职责,其代码需维持在同一个抽象层级中。

举个例子,请试着阅读下面这段代码:

def render(request, app_id):
    """视图函数:停止应用"""
    app = App.objects.get(pk=app_id)
    stop_app_services(app.id)
    app.operator = request.user
    app.status = AppStatus.STOPPED
    app.save()

    # 拼装渲染页面所需参数
    storages = Storage.objects.filter_by_app(app)
    status = get_app_status(app)
    return render("app.html", {"app": app, "storages": storages, "status": status})

render 函数中,我们首先根据 app_id 参数拿到应用对象,随后进行一系列操作将其停止,最后完成页面渲染。

假如做一个简单分析,你会发现函数的前半部分属于模型层(操作应用停止),后半部分则属于视图层(准备参数完成渲染)。在阅读 render() 时,我们实际并不想了解太多有关“应用如何停止”的细节(可将其类比成某小说人物的心理活动)。像现在这样把代码混在一起,严重损害了读者的阅读体验。

为了改善体验,我们可以把前半部分代码封装成一个隶属于 App 模型的方法 stop

def render(request, app_id):
    """视图函数:停止应用"""
    app = App.objects.get(pk=app_id)
    app.stop(oprator=request.user)

    # 拼装渲染页面所需参数
    storages = Storage.objects.filter_by_app(app)
    status = get_app_status(app)
    return render("app.html", {"app": app, "storages": storages, "status": status})

这样处理后,函数内的叙事角度就达成了统一。读代码时,读者可沉浸于“视图渲染”这个叙事角度里,不必疲于在不同角度之间穿梭。

5. 了解规则,打破规则

写作是一种极度自由的创造性活动。你会发现,虽然教材上印着大量关于“如何写作”的规则,但人们耳熟能详的许多作品却在不断打破这些规则。

比方说,规则告诉我们:“段落宜短不宜长”。但当我读《八月之光》时,却发现一些段落足有一页纸那么长。再比方说,小学语文老师教我们:“对话应使用引号,最好独立成段”。可在沈阳作家班宇的小说《冬泳》中,通篇都在这样描写人物对话:

隋菲说,挺好,省心。我说,听介绍人说,你在医院上班。隋菲说,以前在,化工厂医院,当护士,现在不了,状态不好,休长假,半年没上班了。我说,也行,好好休息。

当作者选择打破某项规则时,通常有着明确的目的。比方说让文本更风格化、提供某种新奇的阅读体验等,期望达到出奇制胜的效果。

与写作一样,编程领域也有着海量的原则。比如“不要重复自己(Don't Repeat Yourself)”说:一项知识在项目中只能有一种表现形式,因此重复的函数和代码不可取。“得墨忒耳原则(Law of Demeter)”告诉我们:对象方法只能访问与之有直接关系的成员,因此 obj.Method() 可以,但 obj.b.Method() 就违反了原则。

这些五花八门的原则,是许多前辈经验与智慧的结晶,我们首先应尽可能地了解它们,试着将其应用在自己的项目中。但在那之后,更重要的一点是要懂得该何时打破规则。

编程不等于艺术创作,不鼓励人们无限度地追求极致。作家大可花上数年打磨一本传世之作,但程序员在代码上钻牛角尖就很有问题。代码虽来自个人创作,但编程却是一项多人参与的协作工程类事务。很多时候,身为程序员的我们需要权衡利弊,有选择性地打破某些原则,才能在有限的时间内交出令各方满意的答卷。

结语

虽然编程和写作很像,甚至编程时的部分工作(比如写注释、写文档)就是写作,但两项活动仍有着许多本质上的区别。本文试着从一些角度上寻找二者的相似性,并总结了几条经验之谈。但不论编程还是写作,笔者都属于“半桶水晃荡”的水平,许多观点可能并不专业,还望海涵。

不过有一点我能确定的是:不论编程还是写作,只有“写”才是提升能力的唯一途径。读完 100 本写作教程、翻完 1000 个开源项目的代码,也无法让我们成为大师。所以还等什么呢?关上文章,现在就开始写吧!