每天推荐一个 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.
I 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 if
s 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/1
functions. 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!/1
though. 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 benchee
documentation, 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:
- Initialize
Postgrex
connection - Reset
EventStore
- Take the given inputs as a configuration (e.g. number of accounts)
- Prepare data for tests (e.g. create users, log them into the app)
- 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 Connection
module 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
好的,为了以后不被人想打死,一定会记到的(¬_¬)