域 嵌入图像显示不出来
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 // 5
lights_in_row = 4
return 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/
域 嵌入图像显示不出来