域 嵌入圖像顯示不出來
Code should clearly reflect the problem it’s solving, and thus openly expose that problem’s domain. Embedding domain concepts in code requires thought and skill, and doesn't drop out automatically from TDD. However, it is a necessary step on the road to writing easily understandable code.
代碼應該清楚地反映它正在解決的問題,從而公開暴露該問題的領域。 將領域概念嵌入代碼中需要思想和技巧,并且不會自動從TDD中退出。 但是,這是編寫易于理解的代碼的必經之路。
I was at a software craftsmanship meetup recently, where we formed pairs to solve a simplified Berlin Clock Kata. A Berlin Clock displays the time using rows of flashing lights, which you can see below (although in the kata we just output a text representation, and the lights in a row are all the same colour).
我最近參加了一次軟件手Craft.io聚會,在那里我們結成對,以解決簡化的Berlin Clock Kata。 柏林時鐘使用閃爍的行顯示時間,您可以在下面看到(盡管在kata中,我們只是輸出文本表示,并且行中的所有顏色都是相同的顏色)。
初始測試驅動解決方案 (Initial Test Driven solution)
Most pairs used inside out TDD, and there were a lot of solutions that looked something like this (complete code available on GitHub).
大多數對在TDD內使用,并且有很多解決方案看起來像這樣(完整的代碼可在GitHub上找到 )。
def berlin_clock_time(julian_time):hours, minutes, seconds = list(map(int, julian_time.split(":")))return [seconds_row_lights(seconds % 2), five_hours_row_lights(hours), single_hours_row_lights(hours % 5), five_minutes_row_lights(minutes), single_minutes_row_lights(minutes % 5)]def five_hours_row_lights(hours):lights_on = hours // 5lights_in_row = 4return lights_for_row("R", lights_on, lights_in_row)# ...
This type of solution drops out naturally from applying inside out TDD to the problem. You write some tests for the seconds row, then some tests for the five hours row, and so on, and then you put it all together and do some refactoring. This solution does expose some of the domain concepts at a glance:
這種類型的解決方案會自然而然地從將TDD應用于問題之外而退出。 您為秒行編寫了一些測試,為五小時行編寫了一些測試,依此類推,然后將它們放在一起并進行一些重構。 此解決方案確實使一些領域概念一目了然:
- There are 5 rows 有5行
- There is one second row, 2 hour rows and 2 minute rows 一秒行,2小時行和2分鐘行
Some more concepts are available after a bit of digging, but aren't immediately obvious. The rows are made up of lights that can be on (or presumably off), and that the number of lights on is an indication of the time.
經過一些挖掘,還可以使用其他一些概念,但是這些概念并不是立即顯而易見的。 這些行由可以點亮(或可能熄滅)的燈組成,并且點亮的次數表示時間。
However there are some big parts of the problem that are not exposed. And since I haven't yet explained it, you probably don't know exactly how the Berlin Clock works yet.
但是,有很多問題尚未解決。 而且由于我尚未解釋,您可能還不知道確切的柏林鐘如何工作。
提升概念 (Elevate the concepts)
To improve this we can bring some of the details that are buried in the helper functions (such as get_five_hours
) closer to the top of the file. This brings you to something like the following (complete code available on GitHub), although the downside is that it breaks nearly all of the tests. Solutions like this are rarer on GitHub, but do exist.
為了改善這一點,我們可以將一些輔助函數中隱藏的細節(例如get_five_hours
)放在文件頂部附近。 這帶來了類似以下的內容(完整的代碼可在GitHub上找到 ),盡管缺點是它幾乎破壞了所有測試。 這樣的解決方案在GitHub上很少見,但確實存在。
def berlin_clock_time(julian_time):hours, minutes, seconds = list(map(int, julian_time.split(":")))single_seconds = seconds_row_lights(seconds % 2)five_hours = row_lights(light_colour="R",lights_on=hours // 5,lights_in_row=4)single_hours = row_lights(light_colour="R",lights_on=hours % 5,lights_in_row=4)five_minutes = row_lights(light_colour="Y",lights_on=minutes // 5,lights_in_row=11)single_minutes = row_lights(light_colour="Y",lights_on=minutes % 5,lights_in_row=4)return [single_seconds,five_hours,single_hours,five_minutes,single_minutes]# ...
This improves the concepts that are now exposed at a glance:
這改進了現在可以一目了然的概念:
- There are 5 rows 有5行
- The seconds row is a special case 秒行是一種特殊情況
- There are 2 hour rows and 2 minute rows 有2小時行和2分鐘行
- The rows use different colour lights 行使用不同顏色的燈光
- The rows have a different number of lights 行有不同數量的燈
This is pretty good, and is already better that most of the solutions out there. However, it's still a bit mysterious how the rows are related to each other (there are 2 rows to display the hours and the minutes, so presumably these are linked). It's also not obvious what amount of time each light represents.
這非常好,并且已經比大多數解決方案更好。 但是,各行之間的相互關系還是有點神秘(有兩行顯示小時和分鐘,因此大概是鏈接在一起的)。 每個燈代表多少時間也并不明顯。
命名隱式概念 (Name implicit concepts)
At the moment some of the concepts (such as the amount of time each light represents) are implicit in the code. Making these explicit, and naming them, forces us to understand them and to embed that understanding in the code.
目前,某些概念(例如每盞燈所代表的時間)已隱含在代碼中。 將它們明確顯示并命名,迫使我們理解它們并將這種理解嵌入代碼中。
In order to make the amount of time each light represents explicit, it seems like it would be sensible to pass a time_per_light
value to row_lights
. This means we have to push the calculation of lights_on
down into row_lights
.
為了使每個時間指示燈代表明確的量,現在看來似乎是明智的一傳time_per_light
值row_lights
。 這意味著我們必須將lights_on
的計算向下推到row_lights
。
This in turn makes it obvious that there are two kinds of rows: one related to the quotient (\\
) of the time value, and one related to the remainder / modulus (%
). If we look at the quotient case, we see that the 2nd parameter to the operation is the time_per_light
, which is 5 in both cases (5 hours in one case and 5 minutes in the other).
這樣就可以很明顯地看到兩行:一行與時間值的商( \\
)有關,另一行與余數/模數( %
)有關。 如果看商數情況,我們會發現該操作的第二個參數是time_per_light
,在兩種情況下均為5(一種情況下為5小時,另一種情況下為5分鐘)。
This allows us to write these rows like this:
這使我們可以像下面這樣寫這些行:
five_hour_row = row_lights(time_per_light=5,value=hours, light_colour="R",lights_in_row=4)
If we now turn our attention to the remainder case, we realise that time_per_light
is always singular (one hour or one minute), as it is filling in the gaps in the quotient case.
現在,如果我們將注意力轉向其余情況,我們將意識到time_per_light
總是單數(一小時或一分鐘),因為它填補了商情況中的空白。
For example, the five hours row can represent 0, 5, 10, 15, or 20 hours, but nothing in between. In order to represent any hour, there must be another row to represent +1, +2, +3 and +4. This means that this row must have exactly 4 lights, and that each light must represent 1 hour.
例如,五小時行可以表示0、5、10、15或20小時,但中間沒有任何時間。 為了表示任何小時,必須有另一行表示+ 1,+ 2,+ 3和+4。 這意味著該行必須正好有4個燈,并且每個燈必須代表1個小時。
This implies that the remainder case is dependent on the quotient one, which most people would describe as a parent / child relationship.
這意味著剩余的情況取決于商,大多數人將其描述為父母/子女關系。
With this knowledge in hand, we can now create a function for the child remainder rows, and the solution now looks like this (complete code on GitHub):
掌握了這些知識之后,我們現在可以為子余數行創建一個函數,解決方案如下所示( 在GitHub上完整的代碼 ):
def berlin_clock_time(julian_time):hours, minutes, seconds = list(map(int, julian_time.split(":")))return [seconds_row_lights(seconds % 2),parent_row_lights(time_per_light=5,value=hours, light_colour="R",lights_in_row=4),child_remainder_row_lights(parent_time_per_light=5,value=hours,light_colour="R"),parent_row_lights(time_per_light=5,value=minutes, light_colour="Y",lights_in_row=11),child_remainder_row_lights(parent_time_per_light=5,light_colour="Y",value=minutes)]# ...
A quick glance at this code now reveals nearly all the domain concepts
快速瀏覽一下此代碼,現在可以發現幾乎所有領域的概念
- The first row represents the seconds and is a special case 第一行代表秒,是一種特殊情況
- On the second row each "R" light represents 5 hours 在第二行,每個“ R”燈代表5個小時
- The third row shows the remainder from the second 第三行顯示第二行的其余部分
- On the fourth row each "Y" light represents 5 hours 在第四行,每個“ Y”燈代表5個小時
- The fifth row shows the remainder from the fourth 第五行顯示了第四行的其余部分
This took something thinking about, which will have cost us some time / money. But we increased our understanding of the problem while we did it, and most importantly we embedded that knowledge in to the code. This means that the next person to read the code will not have to do this, which will save some time / money. Since we spend about 10 times longer reading code than we do writing it, this is probably a worthwhile endeavour.
這需要一些思考,這將花費我們一些時間/金錢。 但是,在執行問題時,我們加深了對問題的理解,最重要的是,我們將這些知識嵌入了代碼中。 這意味著下一個閱讀代碼的人將不必這樣做,這將節省一些時間/金錢。 由于我們花費的代碼閱讀時間比編寫代碼的時間長10倍左右,因此這可能是值得的。
Embedding this understanding has also made it harder for future programmers to make mistakes. For example, the concept of parent / child rows didn't exist in earlier examples, and it would be easy to mismatch them. Now the concept is plain to see, and the values are mostly worked out for you. It is also easier to refactor to support new clock variants, for example where lights in the first hours row represent 6 hours.
嵌入這種理解也使將來的程序員更難犯錯誤。 例如,在先前的示例中不存在父/子行的概念,很容易使它們不匹配。 現在可以清楚地看到該概念,并且大多數值都是為您確定的。 重構以支持新的時鐘變體也更加容易,例如,第一個小時行中的燈表示6個小時。
你應該走多遠? (How far should you take it?)
There are things we can do to take this further. For example the parent_time_per_light
of a child row must match the time_per_light
of its parent, and there is nothing enforcing this. There is also a relationship between time_per_light
and lights_in_row
for the parent rows, and again it is not enforced.
我們可以做一些進一步的事情。 例如, parent_time_per_light
子行必須匹配time_per_light
其父,并沒有什么強制執行這一點。 父行的time_per_light
和lights_in_row
之間也存在關系,因此也不再強制執行。
However, at the moment we are only required to support one clock variant, so these probably aren't worth doing. When a change is required for the code, we should refactor so that the change is easy (which might be hard) and then make the easy change.
但是,目前我們只需要支持一個時鐘變體,因此這些可能不值得。 當需要對代碼進行更改時,我們應該進行重構,以使更改變得容易(這可能很困難),然后進行輕松的更改。
結論 (Conclusions)
Embedding domain concepts in code requires thought and skill, and TDD won't necessarily do it for you. It takes longer than a naive solution, but makes the code easier to understand, and will very likely save time in the medium term. Time is money, and finding the right balance of spending time now versus saving time later is also an important skill for a professional programmer to have.
將領域概念嵌入代碼中需要思想和技巧,而TDD不一定能為您做到。 它比幼稚的解決方案花費的時間更長,但是使代碼更易于理解,并且很可能在中期節省時間。 時間就是金錢,對于專業程序員來說,現在要花時間與以后節省時間之間找到合適的平衡也是一項重要技能。
翻譯自: https://www.freecodecamp.org/news/embedding-domain-concepts-in-code/
域 嵌入圖像顯示不出來