程序员优秀之路:一起来看下这 97 位”砖家“能给出啥编程的好建议?(2)

1,085 阅读15分钟

这是我参与8月更文挑战的第2天,活动详情查看:8月更文挑战

本系列共 5 篇,通译自:97-things-every-x-should-know

License:由 CC BY-SA 3.0 获得许可;

欢迎点赞、收藏、评论~ O(∩_∩)O

区分异常

程序运行时出现异常通常可以归为:技术异常业务异常,区分二者有利于我们更好的捕获它们。

技术异常:比如,在长度为 17 的数组访问第 83 位元素,应该将它冒泡到架构设计级别进行统一捕获处理,记录日志、警报管理、输出报告等;

另一种技术异常是由于执行环境的影响。比如数据库无响应了等,应该设有一套基础机制来进行重连,重连合理的次数后仍报失败,再冒泡到统一的技术异常捕获进行处理。

业务异常:比如,尝试从资金不足的账户中取款,客户端应该创建特定、单独的异常层级进行处理,客户端还可以作更多自定义的处理;

混淆二者会让调用者感觉不清晰。

针对训练

“针对训练”与“完成任务”相对立,针对训练可以提高执行任务的技巧和能力;

我们都知道 10000 小时定律。Mary Poppendieck 指出:要经历 10000 小时的针对练习才能成为专家!

10000 小时有多长?每周 20 小时,坚持 10 年!(确实还挺久的~)

其实关键不是 1 万小时,而是 1 万小时有针对的训练,刻意的选择能才让你伟大!

针对训练不是训练你擅长做的事情,它意味着挑战,意味着不断失败,再不断吸取经验、不断成长~

行业黑话

Domain-Specific Languages(DSL)表明:每个领域都有着属于他们独特的语言,本瓜译为 —— 行业“黑话”~

对于开发人员来说,DSL 分为内部和外部:

  • 内部 DSL 是用通用编程语言编写的;

  • 外部 DSL 用文本或图形表达;

我们必须考虑 DSL 的目标受众,他们是开发、还是经理、或是客户?

针对不同的用户需调整不同的语言技术说法、工具名称、语法说明等。同时,借助 DSL 我们还可以隐藏一些技术细节,也能帮助在用户和开发人员之间搭建起一座沟通的桥梁。

语言在逐渐进化,表达方式也会有不同的发展!

勇于打破

我们都接手过一些糟糕的代码,讲道理是:想动,不敢动!

不动,在上面叠加逻辑,可能导致它越来越恶化。就像是医生看病,当病情愈发恶化,你再不动手术的话,就要出大事。

动手术是一时的疼痛,但它会不断往好的方向发展,最终愈合。

当我们再面对槽糕代码时,不要害怕!勇敢牛牛,不怕重构!!

如果能完整的处理它,会帮助你获得很多经验,这都是不可多得的!

重新定义接口、重构模块、重复复制粘贴的代码、减少依赖,加入设计思想......你一定会遇到很多问题,但坚持下来,你最终一定会胜利!

“成为不怕切除肿瘤的外科医生”等于“成为不怕重构槽糕代码的程序猿”。

当然,在这之前,你需要足够的理由说服自己、说服“管理层”。

严于测试

不管是开发做自测,还是测试人员做版本测试,我们可能会随意的写测试数据:123123、testtest、13433334444......有时甚至会将一些私密信息作测试数据~

我们要做这样的考虑:如果这些测试数据被公开了,会不会造成一些麻烦和尴尬?

包括一些提示语也是同样,测试的提示语可能在正式线没有进行替换,导致尴尬。

还包括一些测试的地址在上线前也忘了替换,导致麻烦。

所以,在开发过程中编写的任何测试数据,我们都得尽力严格把控。

别放过错误

如果一个错误不太严重,我们往往会选择忽略,而这往往会带来一些不自知的风险。

try {
    // ...do something...
}
catch (...) {} // ignore errors

说中了,catch 里面的内容本瓜有时会忽略,通常加一句 console.log(err)。

不处理错误会导致:

  • 代码脆弱;
  • 代码不安全;
  • 代码结构差;

别放过错误,别欺骗自己程序总能正常运行、始终有效!

了解语言文化

计算机语言和自然语言一样,我们不仅仅要学它的语法知识,还要学习它背后的文化。

The Pragmatic Programmer 鼓励我们每年学一门新的语言~

了解语言背后的文化是非常有趣的,旦当涉猎,触类旁通。

u1s1,一年学一门,这个想法真的挺大胆的!

一旦你学会了一门新语言的诀窍,它还会反哺你重新认识之前一直在使用的语言。

作者从 Ruby 编程中学会了如何在 C# 中有效地使用委托,在 .NETs 对泛型的使用启发了 Java 中泛型的用法,学习 LINQ 让 Scala 变得轻而易举......

捕获错误

作者为了防止应用程序的终止,用了大量的 try...catch....,却为此付出了代价。

他的团队自制了一个 C++ 的基本应用程序类,它处理了所有转义异常的代码,这导致了每当出现问题时,错误会像在黑帮片被杀的人一样消失,没有留下任何痕迹。

用多次的 try...catch 捕获错误表面上看取代了系统的混乱,但实际上也会产生难以排查的崩溃。

  • 此点存疑:本瓜猜测作者想表达有些错误需要暴露出来在错误处理机制中进行统一处理,而不是写很多 try...catch... 来掩盖。

不相信假设

没有开发经验的管理者觉得程序猿的工作很简单,同样,没有管理经验的开发者,认为管理的工作很简单(不就是合周报吗?🐶)。

编程最困难的部分是思考,而这一部分不那么外露,也就不容易被旁人重视。

在任何项目中,程序猿可能不会积极的参与很多事情:获取用户原始需求、获取预算、构建服务器、部署到生产线、从旧业务中迁移一部分程序等;

当你不积极的参与这些事的时候,你就会无意识的假设:它可能事这样的,这样做那样做应该就行了吧.......

有时,这种假设会成功,有时,则会带来麻烦。

不相信这种假设,问清楚:“什么时候”在“什么条件下”发生“什么事”,而不是“如果”。

不重复自己

在所有编程原则中,不要重复自己 (Don't Repeat Yourself) 可能是最基本的原则之一,它是许多知名软件开发最佳实践和设计模式的基础。

  • 重复是浪费

重复会增加系统的复杂性,导致代码库的膨胀,导致其它开发人员难以理解这个系统。DRY 要求“系统中的每一个点都必须有一个单一的、明确的、权威的表示”

  • 重复的过程可以自动化

软件开发中的许多过程都是重复的,可以实现自动化。自动化过程不易出错,并且还能配套构建自动化测试,进行更方便的测试。

如果你苦于手动做一些重复操作,就应该考虑到:自动化和标准化。

  • 重复的逻辑可以抽象

许多设计模式的重要目标是减少或消除应用程序中的逻辑重复。

如果一个对象在使用之前通常需要发生几件事,这可以通过抽象工厂、工厂模式来完成。如果一个对象的行为有许多可能的变化,则可以使用策略模式,而不是大量使用 if...else... 这些行为结构。

事实上,设计模式本身的制定,就是为了减少解决方案内所需的重复工作。

别动生产线

在大多数基于 Web 的开发环境中,架构可以这样分解:

  • 在开发者机器上进行本地开发和单元测试;
  • 完成手动或自动集成测试的开发服务器;
  • QA 团队和用户在其中进行验收测试的临时服务器;
  • 生产服务器;

开发人员(甚至是高级开发人员)不应拥有超出开发服务器的访问权限,就像 QA 团队和用户也不需要接触开发服务器上的任何东西。

如果出现程序错误,生产线不是修复它的地方!

作者参与过的一些最大的编程灾难是由于违反了这一规则。

封装行为

封装行为,而不是仅仅封装状态。

在软件开发中,封装的价值是有目共睹的。但作者发现:类似乎是开发人员最难掌握的封装结构之一。

如果你的类里面有 3000 多行,说明你还没有完全理解面向对象的思想。

假设我们有三个类:Customer、Order 和 Item。

实现调用:用户消费信用卡

customer.validateCredit(item.price())

如果该方法的后置条件失败,则会抛出异常并中止购买。

经验不足的开发会将业务规则包装到一个叫 OrderManager 或 OrderService 的对象中,在里面记录 Order,Customer、Item,再与 if...else... 进行捆绑。

明显,前者封装行为做的更为出色!后者封装状态,将很难维护。

浮点数不是实数

浮点数不是数学意义上的“实数”,尽管在某些编程语言(例如 Pascal 和 Fortran)中被称为实数。

实数具有无限精度,因此是连续且无损耗的;但浮点数的精度有限,因此它们是有限的。

IEEE 浮点数是基于以 2 为基数的科学记数法的固定精度数 1.d1d2...dp-1 × 2e ,其中 p 是精度。了解浮点数原理可以帮助您避免经典的数字错误。

你不应该在金融应用的程序中使用浮点数,这也是 Python 和 C# 等语言中用十进制类的缘由。

开源实现梦想

也许你没办法在日常工作中满足你雄心勃勃的编程野心,也许你只关心自己的项目想开发创业,也许你想在谷歌、苹果、微软这样的大公司工作.......

好消息,它们有一个共同的答案:开源。通过开源,你可以和世界顶尖编程人员进行对话、可以聚集非常活跃的开发同伴、可以做你任何想做的编程梦~

当然,这就需要你牺牲你得空闲时间。同时,你也需要注意版权、专利、商标和知识产权法等问题。

开源为有上进心的程序员提供了巨大的机会。你觉得你可以,你就真的可以!

API 设计黄金法则

API 设计是非常复杂的,尤其是在大型项目中。你设计它,就一定要考虑到将来如何修改它。

如果你使用 Java 工作,可能会倾向于将大部分类和方法设为 final。

使用 C# 工作,可能可能会密封类和方法。

无论使用哪种语言,你都可能会倾向于:通过单例或使用静态工厂方法来设计 API,以便可以保护它不被覆盖,以及限制别人的使用。

这样真的好吗?

在过去的十年里,作者团队逐渐意识到单元测试是实践中极其重要的一部分,但它并没有完全渗透到整个行业。

API 设计黄金法则:为自己开发的 API 编写测试是不够的;你必须为使用 API 的代码编写单元测试。

这样做,你可以更加清楚的知道他人调 API 所面临的障碍,然后倒逼 API 的设计。

克苏鲁神话

开发过程中,我们偶尔会被同事这样提问:

“我这里这里报这个错,你知道是为什么吗?”

搞笑的是:提出问题的人通常比被问问题的人更有能力作出解答。

克苏鲁神话说人类最原始、最强烈的情感是:恐惧

当我们问别人问题时,首先需要问自己有没有尽全力去排查、检索?

面对恐惧、克服恐惧,你问的那个人真的还不一定比你更懂这个问题!!

努力不代表回报

作为程序员,努力工作往往没有回报。

你可能会欺骗自己和一些同事,让他们相信你在办公室呆了很长时间,对这个项目做出了更多贡献。

但事实上,你也并没有取得更多成就。

如果你每周工作超过 30 小时保持着“高效”工作,那么那么你可能工作太努力了!你应该考虑减少工作量,提高效率。

软件开发耗时 = 开发耗时 + 持续学习耗时

大多数开发工作都像是一场马拉松,你短期内跑的快,不太可能成功。你需要持续的跑,坚持的跑,看清目标的跑。

除了工作,我们可能还需要阅读书籍、参加会议、和其他专业的人交流、尝试新技术等等。因此,你不能总在工作的项目上!

通过寻找聪明的解决方案来尽可能多地做出贡献,提高你的技能,反思你正在做的事情,并调整你的行为。避免让自己表现得像笼子里的仓鼠一样不停在旋转轮上奔跑。

错误追踪

一个好的错误报告需要:

  • 如何更加精准的复现错误;
  • 它的正确表现应该是怎样的;
  • 实际的错误表现是怎样的;

Bug 就像一场对话,所有的历史暴露在每个人面前。不要责怪他人或否认错误的存在。而是询问更多信息。

追踪 bug ,要不断的去更新 bug 的状态,花时间解释下这个错误为什么发生了,清晰的表达让非开发人员也知道。

确保每个人都知道如何找到团队中处理的错误,可以进行追踪反馈。

最后,请记住,目的不是为了记录错误,追责错误,目的是为了解决错误、避免错误。

用删除来改进代码

乔布斯说:less is more,这句话看着挺简单,但是是真的处处都如此。

奥卡姆剃刀原理与之呼应。

我们可以通过删除代码来改进代码。

这些不必要的代码会基于什么样的理由产生:

  • 有人认为将来可能用到它;
  • 有人觉得写了有趣,但不一定有用;
  • 实现起来更容易,比如引入了一个很大的库,但只用其中一小点;
  • 开发理解了额外的需求,实际不存在;

删了只会让你的代码更清晰,just do it ~

易安装

当我们用一个第三方库的时候,很少会仔细去深究其中原理吧。

只要是大致看了功能和 API ,立即就上手安装了。

但是如果安装是个麻烦的话,还有谁会去坚持使用呢?

如果你要做一个插件,请确保它易安装。同样的,接口也是、第三方库也是、组件也是......

易安装、易卸载,是程序被频繁使用的基础。

进程通信导致延迟

响应时间对软件可用性至关重要。

然而,响应时间差的原因却鲜为人知,或者很神秘。

当性能成为应用程序的大问题时,作者的经验是检查数据结构和算法,并寻求改进的方法。但实际上响应时间很大程度上取决于远程进程通信(IPC)数量。每个远程的进程通信都会对整体响应时间产生一些不可忽视的影响,它们加起来就会成为大问题。

比如数据库的调用,如果调用在顺序中执行,延迟会累计,最终导致长时间的响应时间。

再比如包括 Web 服务调用、来自 Web 浏览器的 HTTP 请求、分布式对象调用、请求回复消息传递,以及通过自定义网络协议进行的数据网格交互.......响应所需的远程 IPC 越多,响应时间就越长。

  • 一种解决策略是应用简约原则,优化进程之间的接口,以便用最少的交互量获取正确的数据。
  • 另一种策略是在可能的情况下并行进程间通信,总体响应时间由延迟最长的 IPC 决定。
  • 第三种策略是缓存先前 IPC 的结果,以便可以通过访问本地缓存来避免将来的 IPC。

重视进程通信导致的延迟,比改变数据结构或调整排序算法会带来更多的回报。


OK,以上便是系列第 2 篇分享(共5篇),关注专栏,系列持续追踪~

我是掘进安东尼,输出暴露输入,技术洞见生活,下次再会~