每天推薦一個 GitHub 優質開源項目和一篇精選英文科技或編程文章原文,歡迎關注開源日報。交流QQ群:202790710;微博:https://weibo.com/openingsource;電報群 https://t.me/OpeningSourceOrg


今日推薦開源項目:《為什麼你們就是不能加個空格呢 pangu.js》傳送門:GitHub鏈接

推薦理由:推薦給所有不想看見英文、數字、符號這些和中文擠在一起的朋友,這個項目里提供了 Chrome 和 Firefox 的插件,讓你能夠在瀏覽網頁時再也看不到這樣的現象——插件會幫你加一個空格;而且如果你正在像我一樣一邊核對別人的文章一邊看著他英文中文擠在一起而給他加空格的話,我強烈推薦你使用下面這個工具,一勞永逸。

一勞永逸:https://zizhengwu.github.io/daft-auto-spacing/


今日推薦英文原文:《How to write code which others will understand?》作者:Kamil Lelonek

原文鏈接:https://blog.lelonek.me/how-to-write-code-which-others-will-understand-9005ca818b0f

推薦理由:為什麼我們要寫別人看得懂的代碼,實際上可以用很簡單的方法概括——如果你曾經被別人的代碼搞的頭皮發麻,那麼為了大家都不再遭受這樣的罪,你就應該好好寫代碼,避免讓你的代碼搞的別人頭皮發麻。

How to write code which others will understand?

Do you write your code for other developers or just for yourself?

Code reviews for the greater good

If you know me, you also probably know I like code reviews. Both making and receiving. Every so often, they teach me new things, more often than not, they help me understand a codebase, and, frequently, they let me provide feedback, predict and prevent potential bugs.

strive to not make style-related suggestions (they should be handled by automatic linters and formatters) but I still cannot resist proposing syntax changes in some places (like avoiding ifs or using one-liners even if this is mentioned in our guidelines). Nevertheless, I feel doubtful when I have to review code I don』t understand — either because of an unknown domain or because of… its complexity.

In this article, I』d like to focus on the latter aspect, since the former one is pretty simple to solve — you may leverage the opportunity to ask questions and do deep dives into surrounding code or you just look for someone else who would be a better fit to provide a review.

Your code is not yours

Have you ever wondered who』s gonna read your code? Have you ever thought how complex your code is for others? Have you ever considered the readability of what you』ve written?

Any fool can write code that a computer can understand. Good programmers write code that humans can understand. — Martin Fowler

From time to time, when I see code snippets, I cannot believe in empathy among programmers. Each of us has struggled with a piece of code that was overcomplicated which possibly means each of us has written such a thing as well.

Recently, I』ve seen something like this:

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

  def a ~> b, do: a |> then(b)
end

And that』s OK, maybe someone has lots of fantasy or a mathematical background. I didn』t want to rewrite that immediately but unconsciously I felt something was not right. 「There must be a better way to write it. Let』s see how the code is used」 — I thought. I quickly browsed the codebase to finally find:

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}}
)

Alright, so not only ~> is imported but also someconn!/0 and do_reset/1functions. Let』s see theReset module then:

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

Regarding conn!, there are a couple of solutions to make it simpler but it』s already easy to grasp so I won't niggle over this. I would focus on do_reset!/1though. Not to mention the name, as if it wasn』t complicated enough, the function returns a function that returns its argument and resets the Initializer by the way.

I decided to deconstruct it step-by-step. According to bencheedocumentation, the before_scenario hook takes the input of the scenario as an argument. The return value becomes the input for the next steps. So what the author probably meant was to:

  1. Initialize Postgrex connection
  2. Reset EventStore
  3. Take the given inputs as a configuration (e.g. number of accounts)
  4. Prepare data for tests (e.g. create users, log them into the app)
  5. Run benchmarks

It seems to be quite easy to follow and write. In the entire refactoring, I will neither show you nor change the init function, as it』s not that relevant here.

My first step was to explicitly do aliasing instead of an implicit import. I』ve never really liked magic functions appearing in my code, even though importing Ecto.Query makes queries really elegant. Now, the Connectionmodule looks like:

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

Next, I wanted to write a hook like suggested in the documentation:

before_scenario: fn inputs -> inputs end

The only thing left is to prepare the data. The final result is as follows:

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)

Is the code perfect now? Probably not yet. Is it faster to grasp though? I hope so. Could it be written like that from the very beginning? Definitely!

What』s your problem dude?

When I showed my solution to its owner, I』ve just heard: 「cool」. I didn』t expect anything of course or maybe just a self-reflection from the author.

My biggest problem is that such code worked. Thus, except it was overengineered, I had no arguments to refactor it, as the understandability is a pretty individual opinion. If I have to convince other devs, I will need to ensure they neither get it nor they are able to use it in their own cases. If I have to convince business people, I would probably tell I don』t understand this code, but they may say 「well, you』re just a bad developer then ¯\_(ツ)_/¯」.

It』s (not) management fault

It is not a surprise business people expect results. The cheaper they are and the faster they come, the better. The pressure is on developers though. Managers usually look at software in terms of deadlines, budget, and speed. I』m not blaming them whatsoever here, I』m just trying to explain the possible reasons of bad code because management usually doesn』t care about code quality (at least not directly). Business cares about finding a big lead, making the next sale, cutting costs, and getting new features out yesterday.

When programmers are under pressure, they take many shortcuts. They start writing down what comes to their mind just to make it work, without thinking about maintainability in the future. No one even thinks about tests then. It』s very hard to write elegant code quickly. No matter how experienced you are and what skills you have in general — you need to skip thinking about some things when your time is limited.

Management will be more receptive if you tie poor code quality to wasted money. E.g.: it takes longer to fix bugs and add features, more time is spent bouncing between devs and QA, there』s lower user satisfaction due to high bug count, more customer support resources are needed, etc. I know and I』ve worked with great managers and product owners. They understood the values of the solid codebase. They were able to accept a slower development process which results in faster maintenance in the future though. I also worked with poor business people. The only things they cared were users acquisition, fast product growth, and short-term goals. Many quick wins were taken which led to projects failures.

Quick fix, quick win?

Many times you were probably involved in so-called rapid development. It could happen either to patch some bug quickly or to deliver a proof of concept as soon as possible. More often than not, this involved a dirty and unreadable code. The problem is many times such part of software becomes a production one and has to be maintained and extended without many chances to be refactored. Do we even have time to think about others then?

The role of empathy

Empathy is understanding and being sensitive to the experiences of others without those experiences being explicitly communicated to you.

If you』re developing software, it』s almost always going to be for or with other people. And because software development involves others, empathy can be useful there.

Your code is another form of communication. When architecting and developing a system, we should strive to share an understanding with the people who will interact with our code.

In addition, with empathy, you』re more likely to build maintainable code. Other developers may not think about the advantage that empathy givesthem. They』re quick to sacrifice maintainability for speed. They don』t think about other developers or their future self at the component level of the system. By using empathy as your secret weapon, you』ll write cleaner code with proper documentation.

Empathy allows a developer to comprehend what it』s like for someone else to clean up their mess, and in turn, makes them want to cause fewer messes for others. It helps the developers take responsibility for their own work.

Summary

Recently, I』ve written a code in Elixir like:

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

I immediately thought about Ruby tap method which would help me to rewrite this code like:

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

IMO, that would be more elegant without this intermediate result variable. I thought how I could achieve that in Elixir and with statement came to my mind:

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

The outcome would be the same and the real result would be returned. However, with statement is not intended to do such things and I』m pretty sure it would cause a lot of confusion in this case.

I didn』t decide to do such a thing just for my convenience and I left the initial idea — maybe not so elegant but far more readable. I ask you to do the same, to not overcomplicate your code when it』s not necessary. Think twice before writing something exotic that your colleagues won』t understand and, after some time, you as well.

The only takeaway from this article I have for you is this:
「Please, please always consider OTHERS while writing YOUR code」.


每天推薦一個 GitHub 優質開源項目和一篇精選英文科技或編程文章原文,歡迎關注開源日報。交流QQ群:202790710;微博:https://weibo.com/openingsource;電報群 https://t.me/OpeningSourceOrg