我从痛苦的经验中学到了什么(超过30年的软件开发经验)

图片

这是我30多年来在软件开发中学到的东西的愤世嫉俗的临床作品集。 我再说一遍,有些事情很愤世嫉俗,其余的是在不同工作地点进行长时间观察的结果。

软件开发


首先规格,然后编码


如果您不知道要尝试解决的问题,那么您就不知道要编写什么代码。
首先,在开始编程之前描述应用程序的工作方式。

“没有要求或项目,编程是向空文本文件中添加错误的艺术。”-Louis Sraigley

有时,即使是“简短演示”也足够-最多两个段落描述应用程序的工作。

有时候,由于步骤未完成,我花了更多时间查看代码并想知道下一步该怎么做。 这是时候停止与同事讨论情况了,这是一个好兆头。 也许重新考虑这个决定。

将步骤描述为注释


如果您不知道如何开始,请仅使用您的母语描述应用程序中的顶级数据流。 然后在注释之间填写空白代码。

甚至更好:将每个注释作为一个函数阅读,然后编写一个执行该操作的函数。

小黄瓜可以帮助您实现期望


Gherkin是一种测试描述格式,其原理为:“鉴于系统处于某种状态,如果发生某些情况,那么这是可以预期的。” 如果您不使用了解Gherkin的测试工具,它将使您对应用程序有何期望。

单元测试很好,集成测试更好


在我目前的工作中,我们仅测试模块和类。 例如,我们仅针对表示级别编写测试,然后仅针对控制器级别编写测试,依此类推。 这有助于我们了解一切是否井井有条,但不能让我们看到正在发生的事情的全貌-为此,检查整个系统行为的集成测试非常有用。

测试改善API


我们在级别框架内编程:有一个存储级别可以使我们的数据永存; 有一个处理级别必须以某种方式转换存储的数据; 有一个表示层,其中包含有关数据表示等的信息。

正如我所说,集成测试更好,但是测试级别本身可以使您更好地了解其API的外观。 然后,您可以通过调用某些东西更好地了解这种情况:API是否过于复杂? 我是否需要在附近保存大量数据才能打一个电话?

做可以从命令行运行的测试


我的意思是,命令行本身对于任何对象都不重要,但是您对运行测试的命令的了解,自动执行这些命令的能力,然后可以将它们应用到持续集成工具中。

准备将您的代码发送到购物篮


当您告诉他们可能需要重写许多代码(包括您自己编写的代码)时,许多基于测试(TDD)开始开发的人会感到恼火。

TDD是为了抛出代码而发明的:您对问题的了解越多,您就越了解您写的东西从长远来看并不能解决它。

不用担心。 您的代码不是一堵墙:如果您始终必须丢弃它,那不是浪费。 当然,您已经浪费了编写代码的时间,但是现在您可以更好地理解问题了。

一门好的语言具有综合测试


我向你保证:如果标准语言库具有测试框架-尽管很小,但在与之关联的生态系统中,无论该语言的外部测试框架有何优点,测试都会比没有这种框架的语言更好。

考虑未来意味着浪费您的精力


当开发人员尝试解决问题时,有时他们试图找到一种解决所有问题的方法,包括将来可能出现的问题。

我要告诉你一件事:这些未来的问题将永远不会出现,并且您将不得不伴随着一大堆不会被全部使用的代码,或者由于大量未使用的代码,您将不得不重写所有内容。

现在解决问题。 然后决定以下内容。 然后下一个。 一旦您注意到基于这些决策而出现的模式,只有这样,您才能找到“通用解决方案”。

文档是对自己的未来爱情信息


我们都知道编写有关功能,类和模块的该死的文档是什么类型的出血。 但是,编写此函数或该函数时了解其思想过程可以节省将来的麻烦。

功能文档是其合同


开始编写文档时,您实际上是在创建合同(也许是与您自己签订的合同):“我声称此功能可以做到这一点 ,这就是它的作用。”

如果以后发现您的代码不符合文档说明,那么这将是代码问题,而不是文档问题。

如果函数的描述带有“和”,那么这是不好的


函数只能做一件事。 当您在其中编写文档并看到添加了“和”时,表示该函数执行了其他操作。 将其划分为两个函数并摆脱“和”。

不要将布尔值用作参数


开发函数时,您可能会想添加一个标志。 不要这样做。
让我举一个例子来说明:假设您有一个消息传递系统,并且有一个getUserMessages函数可以将所有消息返回给用户。 但是在某些情况下,您需要返回每条消息的简短摘要(例如,第一段)或整个消息。 因此,您可以添加标记或布尔值形式的参数,称为retrieveFullMessage

同样,不要这样做。

因为那些阅读您的代码的人将看到getUserMessage(userId, true)并且想知道这是怎么回事?

或者,您可以重命名getUserMessageSummaries函数,并输入getUserMessagesFull或类似的名称,但是每个函数只会使用truefalse调用原始的getUserMessage但类/模块外部的接口将是清晰的。
但是不要在函数中添加标志或布尔参数。

当心界面更改


在上一段中,我提到了函数的重命名。 如果您控制使用该功能的源,那么这不是问题,仅是搜索和替换的问题。 但是,如果该函数是由库提供的,则您无需自己动手更改名称。 这将破坏您无法控制的许多其他应用程序,并使许多人感到不安。

您可以创建新函数并将当前函数标记为文档中或代码中不需要的函数。 经过几次释放,您最终可以杀死她。

丑陋的解决方案:创建新功能,将当前功能标记为不良功能,并在功能开头添加sleep以强制更新使用旧功能的功能。

良好的语言具有内置文档


如果该语言使用自己的方式来记录功能,类,模块以及所有其他内容,甚至有一个简单的文档生成器,那么所提及的所有内容都将得到很好的记录(虽然不好,但至少很好)。

而且,没有内置文档的语言的文档质量也很差。

语言不仅仅是语言


您使用编程语言编写程序,并使事情“正常”。 但是其中绝非仅有特殊的单词:该语言具有一个汇编系统,一个依赖管理系统,用于交互的工具,库和框架的工具,一个社区以及一种与人进行交互的方式。

不要选择易于使用的语言。 请记住,您可以找到简单的语法,但是通过选择这种语言,您还可以选择语言的创建者与社区进行交流的方式。

有时候,让应用程序崩溃总比什么都不做更好。


尽管这听起来很奇怪,但最好不要添加错误处理,而要悄悄地捕获它们却什么也不做。

Java有一个可悲的常见模式:

 try { something_that_can_raise_exception() } catch (Exception ex) { System.out.println(ex); } 

这里没有任何例外,仅显示一条消息。

如果您不知道如何处理该错误,请让它发生,这样至少您可以找出发生错误的时间。

如果您知道如何处理,那就去做吧


与上一段相反:如果您知道何时弹出异常,错误或结果,并且知道如何处理,则执行此操作。 显示错误消息,尝试将数据保存在某处,丢弃用户输入的数据,以备日后在日志中使用-对其进行处理

类型讨论您拥有哪些数据


内存只是一个字节序列。 字节只是0到255之间的数字。这些数字的含义在语言类型系统中描述。

例如,在C语言中,值65的字符类型(char类型)可能是字母“ A”,而值65的int将是数字65。

处理数据时请记住这一点。

添加布尔值时,许多人忘记检查True值的数量。 最近,我遇到了这个示例JavaScript:

 console.log(true+true === 2); > true console.log(true === 1); > false 

如果您的数据具有架构,请将其存储为结构


如果数据很简单,例如只有两个字段,那么您可以将它们存储在一个列表中(如果语言允许,也可以存储在一个元组中)。 但是,如果数据具有一种方案(固定格式),则始终使用某种结构或类来存储它。

认识并远离货物邪教


“货运邪教”的思想是,如果有人这样做,那么我们可以做到。 最常见的情况是,货物崇拜只是解决问题的“捷径”:如果X已经做到了,为什么我们应该考虑如何正确存储用户数据呢?

“如果大公司以这种方式存储数据,那么我们可以。”

“如果大公司使用它,那很好。”

“完成任务的正确工具”是一种表达您的意见的方法


短语“完成任务的正确工具”应表示存在某种正确和错误的工具。 例如,使用特定的语言或框架而不是当前的语言或框架。

但是每次我听到某人的这种表达时,人们都会以这种方式推送他们喜欢的语言/框架,而不是说正确的语言/框架。

“正确的工具”比您想象的要明显


也许现在您正在参与一个要在其中处理一些文本的项目。 您可能要说:“让我们使用Perl,因为每个人都知道Perl非常擅长处理文本。”

您忘记了什么:您的团队专门研究C。每个人都知道C,而不是Perl。

当然,如果这是“膝上”的小项目,那么在Perl上是可能的。 如果项目对公司很重要,最好用C编写。

PS:您的英勇项目(下面有更多信息)可能因此而失败。

不适合您项目之外的内容


有时,人们开始使用外部库和框架,而不是使用适当的扩展工具。 例如,直接对WordPress或Django进行更改。

因此,您可以轻松快捷地使该项目不适合维护。 新版本发布后,您将不得不与主项目同步更改,很快您将发现您不再应用更改,并且旧版本的外部工具充满安全漏洞。

数据流击败模式


这是我个人的看法。 如果您了解数据应如何遍历代码,那么对他来说,比使用一堆设计模式要好。

设计模式用于描述解决方案,而不是查找解决方案。


再次是我个人的看法。 根据我的观察,最常见的设计模式是用于找到解决方案。 结果,解决方案(有时是问题本身)被扭曲以适应模式。

首先,解决您的问题。 找到一个好的解决方案,然后在模式中搜索以了解您的解决方案被称为什么。

我已经看过很多次了:我们有一个问题,该模式接近正确的解决方案,让我们使用该模式,现在我们需要向正确的解决方案中添加一堆所有内容,以便它与该模式匹配。

学习函数式编程的基础


您无需深入研究“什么是单子”或“它是函子”的问题。 但是请记住:您不应该不断更改数据; 创建具有新值的新元素(将数据视为不可变的); 尽可能执行不存储内部状态的函数和类(纯函数和类)。

认知努力是可读性的敌人。


认知失调 ”是一种隐晦的表述,“为了理解这一点,我需要同时记住两个(或更多)不同的事物。” 而且这些信息越间接,您就需要花费更多的精力来掌握这些信息。

例如,添加布尔值以计算True值是认知失调的一种温和形式。 如果您阅读了代码并看到了sum()函数,就可以知道该函数将列表中的所有数字相加,那么您期望看到一个数字列表。 我遇到了使用sum()在布尔值列表中计算True值的人,这完全令人困惑。

魔术数字七正负二


魔术数字 ”是心理学上的一篇文章,描述了一个人在短期记忆中能够同时持有的元素数量。

如果您有一个函数调用一个函数,该函数调用一个函数,该函数又调用一个函数,然后又调用一个函数,该函数又调用了一个函数,那么这对于您的代码阅读者来说简直是地狱。

试想一下:我将获得此函数的结果,将其传递给第二个函数,获取其结果,传递第三个函数,依此类推。

而且,今天,心理学家更多地谈论的是神奇数字四,而不是七。
考虑“函数的组合”类别(例如,“我先调用此函数,然后再调用...”),而不考虑“函数的调用”(例如,“此函数将调用该函数,它将调用该函数”)。 ..“)。

削减是好的,但仅限于短期


许多语言,库和框架都提供了快捷方式来减少您键入的字符数。

但是稍后又回到您的手中,您将被迫删除所有内容并完整地编写所有内容。
因此,在使用特定缩写词之前,请先找出其含义。
您无需先写完整的内容,然后再将其更改为缩写:执行缩写为您所做的事情,至少您将了解可能出了什么问题,或者如何用未删节的版本替换某些内容。

抵制“轻松”的诱惑


当然,IDE将帮助您自动完成一堆事情,并使构建项目变得容易,但是您甚至了解其中发生了什么吗?

您知道您的构建系统如何工作吗? 如果必须在没有IDE的情况下运行它,可以这样做吗?

您还记得没有自动完成的功能名称吗? 是否有可能破坏某些内容或重命名它以便于理解?

对引擎盖下发生的事情感兴趣。

始终使用日期中的时区


处理日期时,请始终添加时区。 您总是会遇到时区在计算机和服务器上不匹配的问题,并且会浪费很多时间进行调试,试图了解界面显示错误时间的原因。

始终使用UTF-8


您将遇到与日期相同的编码问题。 因此,始终将字符串值转换为UTF-8,将其存储在UTF-8的数据库中,然后从您的API返回UTF-8。

您可以转换为任何其他编码,但是UTF-8击败了编码之战,因此最简单的坚持方法。

开始愚蠢


摆脱IDE的方法之一是“以愚蠢的方式启动”:只需使用编译器,带有代码突出显示功能的ANY编辑器,以及-进行编程,构建,运行。

是的,这并不容易。 但是稍后使用某种IDE时,您只会想到“是的,它会启动”按钮。 这正是IDE所做的。

日志用于事件,而不用于用户界面。


很长时间以来,我一直使用日志向用户显示应用程序发生了什么。 好吧,因为一件事比两件事要容易得多。

要通知用户事件,请使用标准输出表单。 对于错误报告,标准错误消息。 并且仅将日志用于存储以后可以轻松处理的数据。

日志不是用户界面,而是您需要解析以在适当的时间检索信息的实体。 日志不应是人类可读的。

调试器被高估


我听到许多人抱怨说,没有调试器的代码编辑器太糟糕了,正是因为他们没有调试器。

但是,当代码运行时,您将无法运行您喜欢的调试器。 天哪,您甚至无法运行自己喜欢的IDE。 但是日记...它无处不在。 您可能在跌倒时没有所需的信息(例如,由于日志记录级别不同),但是您可以打开日志记录以稍后查找原因。

我对调试器本身很糟糕的事实不屑一顾,他们只是没有提供许多人期望的帮助。

始终使用版本控制系统


“这只是我想要学习的愚蠢应用程序”-这并不能证明缺少版本控制系统是合理的。

如果您从一开始就使用这样的系统,那么当您犯错时回滚会更容易。

一次提交一次更改


我遇到了在commit中写以下消息的人:“解决问题1、2和3”。 除非所有这些问题都相互重复-其中两个应该已经关闭-否则应该有三个提交而不是一个。

坚持“每次提交更改一次”的原则。 通过更改,我的意思是更改一个文件。 如果需要更改三个文件,请一起提交这些文件。 问问自己:“如果我回滚此更改,什么应该消失?”

“ Git add -p”将帮助您进行很多更改


这仅适用于Git。 它允许您使用“ -p”参数部分合并文件,以便您只能选择彼此相关的更改,而其他更改则保留给新提交。

按数据或类型而非功能来组织项目


大多数项目使用以下结构:

 . +-- IncomingModels | +-- DataTypeInterface | +-- DataType1 | +-- DataType2 | +-- DataType3 +-- Filters | +-- FilterInterface | +-- FilterValidDataType2 +-- Processors | +-- ProcessorInterface | +-- ConvertDataType1ToDto1 | +-- ConvertDataType2ToDto2 +-- OutgoingModels +-- DtoInterface +-- Dto1 +-- Dto2 

也就是说,数据由功能构成(所有输入模型都在一个目录或包中,所有过滤器都在另一目录或包中,等等)。

效果很好。 但是,当您根据数据进行结构化时,将项目划分为较小的项目要容易得多,因为在某个时候,您可能需要做几乎与现在相同的所有工作,并且只有很小的差异。

 . +-- Base | +-- IncomingModels | | +-- DataTypeInterface | +-- Filters | | +-- FilterInterface | +-- Processors | | +-- ProcessorInterface | +-- OutgoingModels | +-- DtoInterface +-- Data1 | +-- IncomingModels | | +-- DataType1 | +-- Processors | | +-- ConvertDataType1ToDto1 | +-- OutgoingModels | +-- Dto1 ... 

现在,您可以制作一个仅适用于Data1的模块,另一个仅适用于Data2的模块,等等。 然后,您可以将它们分成独立的模块。

而且,当您需要创建另一个包含Data1并使用Data3的项目时,可以重用Data1模块中的大多数代码。

制作图书馆


我经常看到开发人员如何创建具有不同项目的大型存储库,或保留不同的分支,而不是使它们成为稍后加入主要部分的临时环境,而仅仅是将项目拆分为较小的部分(谈论拆分为想象模块,而不是构建一个重用Data1类型的新项目,而是使用一个具有完全不同的main函数和Data3类型的分支。

为什么不将常用零件分配给可以在不同项目中连接的库?

多数情况下,原因是人们不知道如何创建库,或者担心如何将这些库“发布”到依赖项资源而又不放弃它们(因此更好地了解您的项目管理工具如何获取依赖项以便您可以创建自己的依赖项存储库?)。

学习监控


在前世中,我添加了许多指标来了解系统的运行方式:系统运行的速度,运行的速度,输入和输出之间的速度,处理的任务数量...

这确实给了系统行为一个好主意。 速度在降低吗? 要了解,我可以检查哪些数据进入系统。 减速在某些时候正常吗?

事实是,如果没有进一步的监控,试图找出系统的“健康状况”是很奇怪的。 “是否响应查询”形式的健康检查不再适用。
尽早添加监视将有助于您了解系统的行为。

使用配置文件


想象一下:您编写了一个函数,您需要传递一个值以使其开始处理(例如,Twitter上的帐户ID)。 但是然后您需要使用两个值来执行此操作,而只需使用另一个值再次调用该函数。

最好使用配置文件,并使用两个不同的配置运行应用程序两次。

命令行选项看起来很奇怪,但是它们很有用


如果将某些内容传输到配置文件,则可以使用户的工作更轻松,并可以选择和打开文件。

如今,对于每种语言,都有可以与命令行选项一起使用的库。 通过为所有内容提供标准的用户界面,它们将帮助您创建一个好的实用程序。

不仅功能组成,而且应用程序组成


Unix使用这个概念:“做一件事情并且做得很好的应用程序”。

我说过,您可以将一个应用程序与两个配置文件一起使用。 如果您需要两个应用程序都提供结果? 然后,您可以编写一个应用程序,该应用程序读取第二个结果,并将所有内容组合为一个通用结果。

即使使用应用程序合成,也要开始愚蠢


应用程序的组成可以发展为微服务(很好),但是它们需要了解应用程序如何通过网络(协议等)彼此“通信”。
无需从此开始。 应用程序可以读写文件,因此非常容易。 当您了解网络后,您将考虑远程交互。

为编译器保留优化


, . «, », , « , ».

, . , .

, , , . , . . . , .


, , . Lisp, . , Python yield , , , . , , , , .



, , . , « », « » ..

, .

,


, .

, , «»: , , , . , , . , , , .

, , . , .


, . (« ?»), .

… Google


, . , Google , . , , Google , .

C/C++ — K&R


. :)

Python — PEP8


PEP8. , .


, ? sleep() .

? ?

, . sleepForSecs sleepForMs , , sleep .

, .

«Zen of Python», .

,


, . , - . - , , , .

« , » — .

: , — . , .

, Java , Rust. , Spring , ++.


, — , «» .

, .


, , - .

— , — .

« , »


« » « ». , , , , .

, .

,


, , , , .

.

( «»), , , . , AWS SQS ( ), , , RabbitMQ.

- , , , .


,


, . , . , .

( , ). , , , .

,


- , , . , , , , .

, . , , , « » « , ».

, .


«». , . , . , , . , .

: «, , , , ». , .

.


. - . «» «».

, , , . , .

, ,


. , , - , .

不要这样做。

- , .


- , . , .

, . .

- ,


: - , . , .

«, , » — , .


, , . . , - . . .

, , . , , , - . , , .

,


« ».

- , , , . : « , , ».

.


, , , . .

, .

«»


«» — . , - « », - .

, , , . , , , .

.

, , «»


. - : «, , ?»

, . , , (, , ).

, —


, -, , , , ( ).

… , - , , « !».

:


«» , , , , . , , .

, /, .

, .

- .

« » « »


: - , , , .

« » — , .

.

,


, , - , .

- .

, .

, , .

, , , .

… .

IT


.

, , 15 , 3-4 .

.

.

, , , - , , , , , , .


. , , URL, .

Trello — ,


« , », .

,


, « , », « , ».

. . , - .

, .

.

, , .


, . , « ». - « », , .

. .


Github «, » . , - .

.

: Python, , Java Python, .

«, »


, , , «, ».

- , , - . , .

Source: https://habr.com/ru/post/zh-CN456862/


All Articles