内容简介:Wow, there is a lot to unpack here. Boring? Art?I said it’s boring because you keep writing code that looks like blocks and blocks of text having the same shape. Assignments, conditionals, loops, functions, etc. I can pull out the best, the most intell
Wow, there is a lot to unpack here. Boring? Art?
I said it’s boring because you keep writing code that looks like blocks and blocks of text having the same shape. Assignments, conditionals, loops, functions, etc. I can pull out the best, the most intelligent code on this planet and put that side by side with another dumb piece of code that someone just wrote for fun. And they look the same from afar.
How boring is it to produce something of the same shape over and over again. And yet, believe it or not, I’ve been doing that for almost 30 years… both professionally and as a hobby. So there is something interesting that keeps dragging me into doing this.
Call it engineering, or whatever you want, but I would consider programming an art. The practice of writing good code requires not just discipline but a different way of logical reasoning that leads to a different world of beauty.
Let me explain.
Everyone knows how to write simple programs that look like linear, step-by-step recipes. That is not what I’m talking about. Practical problems require something more complex. To solve real-world problems, software engineers have to think differently. I shall repeat - think differently.
The secret sauce is abstraction, or so-called generalization. Real good engineers pull themselves out of the practical terms and think deeper with the more general terms.
An example: website health checks
Let’s say I want to write a program that makes a request to a web site and alerts someone if the website is down. The function can simply be written as follows in the Julia language :
function check(url) response = HTTP.get(url) if response.status != 200 then email("someone@somewhere.com", "something is wrong") end end
There is nothing wrong with this function. Really. It does exactly what it should do with the least amount of code. In fact, this is probably what I want to deploy in production as well.
But, it is inflexible.
It is inflexible because it does one thing, and it’s only doing that one thing and nothing else. In other words, it is too rigid. You may wonder, what else do you want, dude?
Just think about that for a little bit. It is not hard to see the following potential problems:
-
The HTTP request only uses the
GET
method without any query parameter. What if I want to use thePOST
method? What if I need to pass some kind of id as part of the query parameters in the URL? -
The code checks the HTTP status against
200
to see if it was successful. What if I want to handle the other 2xx response codes as well? -
The logic for alerting somenoe is hard-coded to send an email. What if I want to send the alert to a chat platform such as Slack or Zulip?
Generalizing the solution
A skillful programmer looks at the problem and says, hey, why don't we build an abstraction layer to make it more flexible? Let’s walk through the refactoring process below.
What we will do is to maintain the structure/logic of the code and abstract away the rest of the code as more general functions:
function check(url) response = fetch(url) if !is_successful(response) then alert(site, response) end end
The fetch
function hits the URL and returns some kind of response object. The is_successful
function analyzes the response object and decides if it was successful or not. Finally, the alert
function notifies someone about the problem.
These functions are pure abstractions because there is no need to know how they do their job. It’s the same thing as great employees. You don’t need to tell them how to get the job done. They just know and get the job done.
Coming back to the example, the alert
function may send an email, log an event to a database, page the support person, or ask Siri to wake you up! You know what I mean. The point is that the check
function contains the same logic no matter what these little functions do!
But, hey, each of these little functions still does one thing, right?
Great question. Indeed, that’s exactly what it does at the moment. Let's do a little more. Primarily, we can give the function some context. The first step is to recognize that we need an abstract concept "Fetcher", or something that knows how to fetch data. Logically, such concept can be coded as an abstract type:
abstract type AbstractFetcher end
Then, fetching something via HTTP and checking the result is just one of the many possible implementations:
struct HTTPFetcher <: AbstractFetcher end fetch(::HTTPFetcher, url) = HTTP.get(url) function is_successful(::HTTPFetcher, response) return response.status === 200 end
Likewise, we can set up the "notifier" abstract concept the same way.
""" A notifier knows how to notify someone. """ abstract type AbstractNotifier end """ Send a notification alert message. """ function alert end
Note that the alert
function has no body. The purpose of having an empty function is to define what the interface looks like and attach a doc string for documentation purpose.
Just for fun, we will implement both an EmailNotifier
and SlackNotifier
here:
struct EmailNotifier <: AbstractNotifier recipient::String end function alert(::EmailNotifier, subject, message) SMTP.send(notifier.recipient, subject, messgae) end struct SlackNotifier <: AbstractNotifier channel::String end function alert(notifier::SlackNotifier, subject, message) Slack.send(notifier.channel, subject, message) end
>>> Note: SMTP and Slack are "made-up" packages for illustration purpose only. They do not exist.
Finally, the check
function just need to accept a fetcher and a notifier object.
function check(url, fetcher::AbstractFetcher, notifier::AbstractNotifier) response = fetch(fetcher, site) if !is_successful(fetcher, response) then alert(notifier, "$site is down", "Error: $response") end end
Just having these few abstractions opens up a lot of possibilities. Why? Let’s suppose that you have 3 different kinds of fetchers and 3 kinds of notifiers. Now, you can easily compose 3 x 3 = 9 different kinds of health checks.
Please beware that abstraction is a double-edged sword.
Too much abstraction can hurt. In the above example, there is only a single level of abstraction. When one looks at the code, it is still pretty easy to understand and comprehend. However, if there are multiple layers of abstraction, then it becomes very difficult to reason about the code. Look no further than Fizz Buzz Enterprise Edition for an extreme illustration of over-engineering.
As you can see, it is quite simple to uplift your code to a new level. The use of abstract type is a quick and easy way to build these absractions. Note that there are other paradigms - for example, traits. I will save that for a future post. If you cannot wait, head over to the BinaryTraits.jl project repo for a quick peek of what's brewing at the moment.
Programming can be fun. It does not have to be boring. At this point, I guess I will retract my initial claim. While my code may look boring from afar, when I look closer, I see a beautiful thing.
Enjoy your programming life. It's a fun art!
以上所述就是小编给大家介绍的《Programming Is Boring Art》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Java程序设计与应用开发
於东军 / 清华大学出版社 / 2005-3 / 27.00元
本书作为Java程序的入门与应用教材,共分为3部分:第一部分讲解Java程序设计的基础知识,包括Java基本编程语言、面向对象设计思想、类、对象、接口以及异常处理。第二部分讲解Java程序设计的高级知识,包括:GUI编程、套接口编程、I/O系统、数据库访问以及多线程编程。第三部分详细分析一个实际项目的开发过程,包括系统分析及功能实现。在项目实例中综合应用第一、二部分的Java知识,能够帮助读者进一......一起来看看 《Java程序设计与应用开发》 这本书的介绍吧!