如何编写每个人都会理解的代码?


译者:我们为您发表了 Camil Lelonek 的文章,内容涉及代码可读性和“程序员的同理心”。

您是否想知道谁将查看您的代码? 对别人来说有多困难? 试图确定其可读性
“任何傻瓜都可以编写机器能够理解的代码。 但是只有优秀的程序员才能编写人们理解的代码,”马丁·福勒(Martin Fowler)说。
有时,当我看到一些代码片段时,我对程序员之间同理心的存在不信任。 您必须了解我在说什么-因为我们每个人都遇到过写得很糟糕并且几乎不可读的代码。
Skillbox建议:两年实践课程“我是PRO Web开发人员”

我们提醒您: 对于所有“ Habr”读者来说,使用“ Habr”促销代码注册任何Skillbox课程时均可享受10,000卢布的折扣。
我最近看到了这样的内容:

defmodule Util.Combinators do def then(a, b) do fn data -> b.(a.(data)) end end def a ~> b, do: a |> then(b) end 

原则上,这里一切都很好:也许有人只是幻想,或者代码的编写者具有扎实的数学背景。 我不想重写此代码,但是在我下意识地感觉这里有些问题。 “必须有一种方法使它变得更好,用不同的方式表述它。 我会看到一切的运作方式,“我是这样认为的。 很快,我发现了这一点:

 import Util.{Reset, Combinators} # ... conn = conn!() Benchee.run( # ... time: 40, warmup: 10, inputs: inputs, before_scenario: do_reset!(conn) ~> init, formatter_options: %{console: %{extended_statistics: true}} ) 

嗯,看起来不仅要导入〜>,而且要导入conn!/ 0和do_reset!/ 1函数。 好的,让我们看一下“重置”模块:

 defmodule Util.Reset do alias EventStore.{Config, Storage.Initializer} def conn! do {:ok, conn} = Config.parsed() |> Config.default_postgrex_opts() |> Postgrex.start_link() conn end def do_reset!(conn) do fn data -> Initializer.reset!(conn) data end end end 

至于conn!,有两种方法可以简化此站点。 然而,在这一点上停止是没有意义的。 我宁愿专注于do_reset!/ 1。 该函数返回一个函数,该函数返回一个参数并为Initializer进行复位。 而且名称本身很复杂。



我决定对代码进行反向工程。 根据同事的文档,before_scenario将脚本输入作为参数。 返回值将成为后续步骤的输入。 这可能是作者的意思:

  • 初始化Postgrex连接。
  • 重置事件存储。
  • 使用输入值作为配置项(谈论帐户数)。
  • 准备测试数据(即创建用户并进入应用程序)。
  • 使用基准。

通常,一切都很清楚,此类代码易于编写。 我注意到在重构过程中,我不会显示或修改init函数,这在这里不是很重要。

第一步是显式使用别名而不是隐式导入。 尽管Ecto.Query使查询变得优雅,但我从不喜欢代码中出现的“魔术”功能。 现在,我们的连接模块如下所示:

 defmodule Benchmarks.Util.Connection do alias EventStore.{Config, Storage.Initializer} def init! do with {:ok, conn} = Config.parsed() |> Config.default_postgrex_opts() |> Postgrex.start_link() do conn end end def reset!(conn), do: Initializer.reset!(conn) end 

接下来,我决定按照文档中的建议编写一个“钩子”:

before_scenario:fn输入->输入结束

剩下要做的就是准备数据。 最终结果如下:

 alias Benchmarks.Util.Connection conn = Connection.init!() # ... Benchee.run( inputs: inputs, before_scenario: fn inputs -> Connection.reset!(conn) init.(inputs) end, formatter_options: %{console: %{extended_statistics: true}} ) Connection.reset!(conn) 

这个代码完美吗? 可能还没有。 但是更容易理解吗? 我希望如此。 可以马上完成吗? 绝对可以。

怎么了


当我向作者提出解决方案时,我听到:“很酷。” 但是,我没想到更多。

问题在于主要代码有效。 使我思考重构需求的唯一原因是代码的结构过于复杂且可读性较低。

为了说服其他开发人员使其代码可读,您需要使人信服的东西。 而且“我决定重做您的代码,因为它晦涩难懂”这一论点将不被接受,答案是“意味着您只是一个糟糕的开发人员,我该怎么做?\ _(ツ)_ /¯”。



这是(非)管理问题


没有人会对企业期望员工的业绩感到惊讶。 而且越早收到它们越好。 经理通常根据截止日期,预算,速度来评估软件及其编写。 我并不是说这很不好,我只是在解释为什么没有那么高品质的代码“行之有效”。 事实是,经理对美观和可读性不是很感兴趣,他们需要销售,低成本和快速的结果。

当程序员受到压力时,他们寻求出路。 通常,解决方案是创建一个“工作代码”,其中可能会有很多“拐杖”。 它的创建没有想到将来需要维护代码。 优雅的代码很难快速编写。 程序员的经验水平如何都没有关系,在时间压力下完成工作时,没有人会想到美。

这种情况只能通过说服经理们认为不良(尽管有效)代码有可能在不久的将来增加其维护成本来解决。 修复错误并添加新功能,查看,编写技术文档-在代码质量低下的情况下,与优雅而合理的情况相比,所有这些花费的时间要多得多。



同情的重要作用


如果您正在开发软件,那么您将了解该软件是供其他人使用的,并且开发是在团队中进行的。 同情在这里非常重要。

您的代码是一种特殊的交流形式。 在开发未来软件的体系结构的过程中,您需要考虑那些将与您的代码进行交互的人员。

“程序员的怒气”有助于创建更简洁,更合理的代码,即使最后期限已到,而经理也在不断“施压”。 它有助于理解解析别人的不可读代码的感觉,这很难理解。



结论


我最近在Elixir上编写了代码:

 result = calculate_results() Connection.close(conn) result 

然后我想到了一个允许您重写以下代码的Ruby tap方法:

 calculate_result().tap do Connection.close(conn) end 

恕我直言,这样做最好是没有中间变量的结果。 我考虑了如何做到这一点,并得出以下结论:

 with result = calculate_results(), Connection.close(conn), do: result 

结果将是相同的。 但是使用with可能会对学习此代码的人造成问题,因为在通常情况下,with的用法不同。

因此,我决定保留一切,以免牺牲他人的自身利益。 我要求您做同样的事情,而又不要使代码变得无法估量。 在介绍一些奇异的函数或变量之前,请记住,您的同事可能根本不理解评论中的内容。

通常,我建议使用以下原则:“编写代码时,请考虑一下其他”。

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


All Articles