sh1marin's blog

8个开发者需要的好习惯

今天看到的一个很有价值的文章,决定自行翻译,英语水平有限,有歧义欢迎指出,原文地址

最佳做法 (Best Practices)

这段标题仍有改进空间

#1: 把解决问题放在第一位

许多贡献者都十分具有创造力,并且他们十分享受设计抽象数据结构,创造友好的用户界面,亦或只是简单享受编程的过程。不管是什么情况,他们常常会产生一些很棒的想法,但是这些想法很多时候可能对解决实际问题并没有什么帮助。

这常常被称为 用办法寻找问题 。这种做法可能没有什么坏处,但是在现实生活中,你需要花时间去写这些没有太大用处的代码,写好的代码需要占用空间来编译和存放,且一段代码写好了就需要你花费精力去维护,这对你来说并没有什么太大的益处。对于软件开发者而言,不增加任何无意义的额外工作永远被认为是一种最好的做法。

#2:解决最关键的问题

这一段是对上一个话题的延伸。我们已经知道增加任何多余的东西不是一个好做法,但是什么因素能决定这么做是必要的亦或是不必要的呢?

答案就是:我们需要解决的这个问题必须先实际存在,而不是靠臆想产生出来的。用户使用软件旨在创造一些他们需要的东西,在这个过程中,他们可能会陷入一些问题导致工作不得不暂停,或是生产力被这些问题所拖累。在这种情况下,我们才需要去解决这个问题。

相信某个问题未来总是会出现,所以软件必须提前做好准备,在问题出现时立刻解决,这种行为被称为“未来打样”。通常表现为:

  • 我觉得这样可能对用户会比较有用…
  • 我觉得用户最终会需要…

然而这常常被认为是一种坏习惯。因为总是为未出现的问题设计代码会导致这段代码可能并不会被使用到,或者会导致代码比起实际更加难以使用和维护。

#3:解决那些比较复杂或常常出现的问题

软件虽然被设计出来的目的是解决问题,但是我们并不能指望它能解决世界上出现的所有问题。作为一个游戏引擎,Godot 会为你解决游戏制造上的问题,帮助你把游戏做的质量更好,效率更高。但是 Godot 并不能帮助你完成整个游戏。引擎的能力总是有限的。

一个问题是否值得被解决由用户的解决难度来决定,这里的解决难度可以被解释为:

  • 问题的复杂程度
  • 问题的频繁程度

如果这个问题对于绝大部分的用户来说都过于复杂,那么软件就必须提供一个现成的解决方案。同样的,如果这个问题对于用户而言很好解决,那么并没有必要提供解决方案,且这个问题也应该由用户自行解决。

然而,虽然问题解决起来很轻松,但是用户频繁的遇到这个问题,每一次都需要费工夫去解决这么一个小问题,那么在这种情况下,软件就需要提供一种解决方案来简化使用流程来提高使用体验。

在我们的实际经历中,很多时候你是可以很清楚的分辨这个问题是否值得去解决,但是总会出现难以划定标准的时候。这就是为什么我们总是推荐和其他开发者交流。

#4 必须和他人交流自己的解决办法

我们常常会遇到一种情况,当你陷入某个问题时,你可能只会沉迷于自己的项目,因此你会很自然的只站在自己的角度,只从自己的视角来解决问题。因此,你的解决办法不会总考虑到别的程序员已经常常注意的情况。

对于开发者来说,每个开发者的看法都是不一样的,有的人可能会觉得用户的问题实在是太过于独特,不必单独设计解决方案,有的可能会对一些更加复杂的问题建议一些过于片面简单的做法,然后遗留剩下的问题给用户自己去解决。

不管是什么情形,在尝试做贡献时,和别的开发者讨论你在实际正在解决的问题是十分重要的,因此也能达成一个更多人赞同的实践。

只有在一种情况下是特殊的,当在某片领域还没有人做出令大部分人满意的贡献时,某些于用户直接交流并拥有丰富知识的人可以直接做出解决方案。

同时,Godot 的开发哲学是,更易用和更易维护比绝对的性能更重要。性能优化会在后期考虑,但是假如为了性能而牺牲易用性,牺牲代码清晰度,这是无法被接受的。

#5 不同的问题用不同解法

对于程序员来说,最有意思的挑战就是去找到各种问题的最佳解法。然而问题可能会过多,导致程序员尝试用一种解法去应对各种问题。

当你想要让解决办法更加灵活多变,能适应各种问题,情况就会变得更加糟糕,而我们在第二节提到的 “臆想出来的问题” 也会在这种情况下出现。

最主要的问题就是,在现实中,很少会需要用到这种解法的。绝大部分时间,对于每个独立的问题,用单独的代码去解决他们会更加简单且更易于维护。

另外,用来分割问题的解法对于用户也更加友好,他们可以不需要学习并记忆整个复杂的系统,他们只需要他们需要用的东西。

庞大且灵活的解法还有一个弊病就是,随着时间的推移,他们的灵活性会逐渐下降,这就要求程序员不断的往里添加新的方法,而这会导致代码和 API 越来越复杂。

#6 迎合更加常见的用户情形,对罕见情形留一条路

这是前面一点的延续,在这节会更加深度的解释为什么我们会推荐这种想法和这种设计方式。

就像我们在第二点提到的,对于一个普通人来说,去了解所有未来可能会出现的需求是十分困难的,尝试去写一个对所有情形都通用的结构常常是一种错误做法。

我们可能会写一些我们觉得很酷的功能,但是在生产环境中,我们可能会发现用户几乎不会用到这些功能,或者他们的需求与我们的最初的开发方向大相径庭,让我们不得不把无用的功能抛弃,或是让整个程序变得更加复杂。

所以问题就是,我们要怎样设计一个软件,可以给予用户一些我们知道他们需要的功能,但是又足够灵活,能给予用户一些我们不知道他们是否需要的功能。

答案如图所示,想要让用户能够随心所欲的使用,我们只需要给他们一些低权限的 API 接口,让他们能够用来满足他们的需求,就算可能会因为重新实施一些重复的逻辑来增加他们的工作量。

在现实生产环境中,这种情况可能很少见,但是设计自定义的解决方案仍然是有意义的,这也是为什么提供基础的设计模块给用户是重要的。

#7: 解决方案必须本地化

当你正在为一个问题寻求解决方案,亦或是实施一些新特性,或是修一个 bug, 很多时候最简单的办法是往核心代码里添加新数据或新方法。

然而这样的做法有个问题,直接把那些只会在一个或两个地方用到的新代码加进核心代码里,不止会让代码很难跟进,还会让整个核心 API 更冗余,更复杂,更加难以理解。

这种写法特别糟糕,建立在大量代码都依赖于核心代码的基础上,而且它也是新的贡献者学习理解项目代码的起点,核心 API 的可读性和干净程度是极其重要的。

通常往核心代码里添加代码的原因是只需要很少的代码就能简单的添加功能。

尽管如此,这种做法仍然不被推荐。通常来说,用来解决问题的代码应该靠近出现问题的地方,即使这会产生更加多重复代码,可能会更加复杂或者低效。当然也可以发挥创造力,但是这种方式总是被推荐的。

#8:不要对简单的问题使用复杂的方案套装

并不是所有的问题都能简单解决,很多时候正确的选择是去用那些第三方的社区库来帮助解决问题。

因为 Godot 需要实现跨平台,我们不能动态的连接这些库,我们选择将他们捆绑进我们的源码里。

也正因为如此,我们会非常挑剔的选择捆绑的代码且我们会更加偏向于选择体积比较小的库。只有在没得选的情况下我们才会捆绑那些巨大的库。