forked from DlangRen/Programming-in-D
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcontracts.d
392 lines (293 loc) · 13.3 KB
/
contracts.d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
Ddoc
$(DERS_BOLUMU $(IX contract programming) Contract Programming)
$(P
Contract programming is a software design approach that treats parts of software as individual entities that provide services to each other. This approach realizes that software can work according to its specification as long as the provider and the consumer of the service both obey a $(I contract).
)
$(P
D's contract programming features involve functions as the units of software services. Like in unit testing, contract programming is also based on $(C assert) checks.
)
$(P
Contract programming in D is implemented by three types of code blocks:
)
$(UL
$(LI Function $(C in) blocks)
$(LI Function $(C out) blocks)
$(LI Struct and class $(C invariant) blocks)
)
$(P
We will see $(C invariant) blocks and $(I contract inheritance) in $(LINK2 /ders/d.en/invariant.html, a later chapter) after covering structs and classes.
)
$(H5 $(IX in, contract) $(IX precondition) $(C in) blocks for preconditions)
$(P
Correct execution of functions usually depend on whether the values of their parameters are valid. For example, a square root function may require that its parameter cannot be negative. A function that deals with dates may require that the number of the month must be between 1 and 12. Such requirements of a function are called its $(I preconditions).
)
$(P
We have already seen such condition checks in the $(LINK2 /ders/d.en/assert.html, $(C assert) and $(C enforce) chapter). Conditions on parameter values can be enforced by $(C assert) checks within function definitions:
)
---
string timeToString(in int hour, in int minute) {
assert((hour >= 0) && (hour <= 23));
assert((minute >= 0) && (minute <= 59));
return format("%02s:%02s", hour, minute);
}
---
$(P
$(IX body) In contract programming, the same checks are written inside the $(C in) blocks of functions. When an $(C in) or $(C out) block is used, the actual body of the function must be specified as a $(C body) block:
)
---
import std.stdio;
import std.string;
string timeToString(in int hour, in int minute)
$(HILITE in) {
assert((hour >= 0) && (hour <= 23));
assert((minute >= 0) && (minute <= 59));
} $(HILITE body) {
return format("%02s:%02s", hour, minute);
}
void main() {
writeln(timeToString(12, 34));
}
---
$(P
A benefit of an $(C in) block is that all of the preconditions can be kept together and separate from the actual body of the function. This way, the function body would be free of $(C assert) checks about the preconditions. As needed, it is still possible and advisable to have other $(C assert) checks inside the function body as unrelated checks that guard against potential programming errors in the function body.
)
$(P
The code that is inside the $(C in) block is executed automatically every time the function is called. The actual execution of the function starts only if all of the $(C assert) checks inside the $(C in) block pass. This prevents executing the function with invalid preconditions and as a consequence, avoids producing incorrect results.
)
$(P
An $(C assert) check that fails inside the $(C in) block indicates that the contract has been violated by the caller.
)
$(H5 $(IX out, contract) $(IX postcondition) $(C out) blocks for postconditions)
$(P
The other side of the contract involves guarantees that the function provides. Such guarantees are called the function's $(I postconditions). An example of a function with a postcondition would be a function that returns the number of days in February: The function can guarantee that the returned value would always be either 28 or 29.
)
$(P
The postconditions are checked inside the $(C out) blocks of functions.
)
$(P
Because the value that a function returns by the $(C return) statement need not be defined as a variable inside the function, there is usually no name to refer to the return value. This can be seen as a problem because the $(C assert) checks inside the $(C out) block cannot refer to the returned variable by name.
)
$(P
D solves this problem by providing a way of naming the return value right after the $(C out) keyword. That name represents the very value that the function is in the process of returning:
)
---
int daysInFebruary(in int year)
$(HILITE out (result)) {
assert((result == 28) || (result == 29));
} body {
return isLeapYear(year) ? 29 : 28;
}
---
$(P
Although $(C result) is a reasonable name for the returned value, other valid names may also be used.
)
$(P
Some functions do not have return values or the return value need not be checked. In that case the $(C out) block does not specify a name:
)
---
out {
// ...
}
---
$(P
Similar to $(C in) blocks, the $(C out) blocks are executed automatically after the body of the function is executed.
)
$(P
An $(C assert) check that fails inside the $(C out) block indicates that the contract has been violated by the function.
)
$(P
As it has been obvious, $(C in) and $(C out) blocks are optional. Considering the $(C unittest) blocks as well, which are also optional, D functions may consist of up to four blocks of code:
)
$(UL
$(LI $(C in): Optional)
$(LI $(C out): Optional)
$(LI $(C body): Mandatory but the $(C body) keyword may be skipped if no $(C in) or $(C out) block is defined.)
$(LI $(C unittest): Optional and technically not a part of a function's definition but commonly defined right after the function.)
)
$(P
Here is an example that uses all of these blocks:
)
---
import std.stdio;
/* Distributes the sum between two variables.
*
* Distributes to the first variable first, but never gives
* more than 7 to it. The rest of the sum is distributed to
* the second variable. */
void distribute(in int sum, out int first, out int second)
in {
assert(sum >= 0);
} out {
assert(sum == (first + second));
} body {
first = (sum >= 7) ? 7 : sum;
second = sum - first;
}
unittest {
int first;
int second;
// Both must be 0 if the sum is 0
distribute(0, first, second);
assert(first == 0);
assert(second == 0);
// If the sum is less than 7, then all of it must be given
// to first
distribute(3, first, second);
assert(first == 3);
assert(second == 0);
// Testing a boundary condition
distribute(7, first, second);
assert(first == 7);
assert(second == 0);
// If the sum is more than 7, then the first must get 7
// and the rest must be given to second
distribute(8, first, second);
assert(first == 7);
assert(second == 1);
// A random large value
distribute(1_000_007, first, second);
assert(first == 7);
assert(second == 1_000_000);
}
void main() {
int first;
int second;
distribute(123, first, second);
writeln("first: ", first, " second: ", second);
}
---
$(P
The program can be compiled and run on the terminal by the following commands:
)
$(SHELL
$ dmd deneme.d -w -unittest
$ ./deneme
$(SHELL_OBSERVED first: 7 second: 116)
)
$(P
Although the actual work of the function consists of only two lines, there are a total of 19 nontrivial lines that support its functionality. It may be argued that so much extra code is too much for such a short function. However, bugs are never intentional. The programmer always writes code that is $(I expected to work correctly), which commonly ends up containing various types of bugs.
)
$(P
When expectations are laid out explicitly by unit tests and contracts, functions that are initially correct have a greater chance of staying correct. I recommend that you take full advantage of any feature that improves program correctness. Both unit tests and contracts are effective tools toward that goal. They help reduce time spent for debugging, effectively increasing time spent for actually writing code.
)
$(H5 $(IX -release, compiler switch) Disabling contract programming)
$(P
Contrary to unit testing, contract programming features are enabled by default. The $(C ‑release) compiler switch disables contract programming:
)
$(SHELL
dmd deneme.d -w -release
)
$(P
When the program is compiled with the $(C ‑release) switch, the contents of $(C in), $(C out), and $(C invariant) blocks are ignored.
)
$(H5 $(IX in vs. enforce) $(IX enforce vs. in) $(IX assert vs. enforce) $(IX enforce vs. assert) $(C in) blocks versus $(C enforce) checks)
$(P
We have seen in the $(LINK2 /ders/d.en/assert.html, $(C assert) and $(C enforce) chapter) that sometimes it is difficult to decide whether to use $(C assert) or $(C enforce) checks. Similarly, sometimes it is difficult to decide whether to use $(C assert) checks within $(C in) blocks versus $(C enforce) checks within function bodies.
)
$(P
The fact that it is possible to disable contract programming is an indication that contract programming is for protecting against programmer errors. For that reason, the decision here should be based on the same guidelines that we have seen in the $(LINK2 /ders/d.en/assert.html, $(C assert) and $(C enforce) chapter):
)
$(UL
$(LI
If the check is guarding against a coding error, then it should be in the $(C in) block. For example, if the function is called only from other parts of the program, likely to help with achieving a functionality of it, then the parameter values are entirely the responsibility of the programmer. For that reason, the preconditions of such a function should be checked in its $(C in) block.
)
$(LI
If the function cannot achieve some task for any other reason, including invalid parameter values, then it must throw an exception, conveniently by $(C enforce).
$(P
To see an example of this, let's define a function that returns a slice of the middle of another slice. Let's assume that this function is for the consumption of the users of the module, as opposed to being an internal function used by the module itself. Since the users of this module can call this function by various and potentially invalid parameter values, it would be appropriate to check the parameter values every time the function is called. It would be insufficient to only check them at program development time, after which contracts can be disabled by $(C ‑release).
)
$(P
For that reason, the following function validates its parameters by calling $(C enforce) in the function body instead of an $(C assert) check in the $(C in) block:
)
---
import std.exception;
inout(int)[] middle(inout(int)[] originalSlice, size_t width)
out (result) {
assert(result.length == width);
} body {
$(HILITE enforce)(originalSlice.length >= width);
immutable start = (originalSlice.length - width) / 2;
immutable end = start + width;
return originalSlice[start .. end];
}
unittest {
auto slice = [1, 2, 3, 4, 5];
assert(middle(slice, 3) == [2, 3, 4]);
assert(middle(slice, 2) == [2, 3]);
assert(middle(slice, 5) == slice);
}
void main() {
}
---
$(P
There isn't a similar problem with the $(C out) blocks. Since the return value of every function is the responsibility of the programmer, postconditions must always be checked in the $(C out) blocks. The function above follows this guideline.
)
)
$(LI
Another criterion to consider when deciding between $(C in) blocks versus $(C enforce) is to consider whether the condition is recoverable. If it is recoverable by the higher layers of code, then it may be more appropriate to throw an exception, conveniently by $(C enforce).
)
)
$(PROBLEM_TEK
$(P
Write a program that increases the total points of two football (soccer) teams according to the result of a game.
)
$(P
The first two parameters of this function are the goals that each team has scored. The other two parameters are the points of each team before the game. This function should adjust the points of the teams according to the goals that they have scored. As a reminder, the winner takes 3 points and the loser takes no point. In the event of a draw, both teams get 1 point each.
)
$(P
Additionally, the function should indicate which team has been the winner: 1 if the first team has won, 2 if the second team has won, and 0 if the game has ended in a draw.
)
$(P
Start with the following program and fill in the four blocks of the function appropriately. Do not remove the $(C assert) checks in $(C main()); they demonstrate how this function is expected to work.
)
---
int addPoints(in int goals1,
in int goals2,
ref int points1,
ref int points2)
in {
// ...
} out (result) {
// ...
} body {
int winner;
// ...
return winner;
}
unittest {
// ...
}
void main() {
int points1 = 10;
int points2 = 7;
int winner;
winner = addPoints(3, 1, points1, points2);
assert(points1 == 13);
assert(points2 == 7);
assert(winner == 1);
winner = addPoints(2, 2, points1, points2);
assert(points1 == 14);
assert(points2 == 8);
assert(winner == 0);
}
---
$(P $(I $(B Note:) It may be better to return an $(C enum) value from this function:)
)
---
enum GameResult {
draw, firstWon, secondWon
}
$(HILITE GameResult) addPoints(in int goals1,
in int goals2,
ref int points1,
ref int points2)
// ...
---
$(P
I chose to return an $(C int) for this exercise, so that the return value can be checked against the valid values of 0, 1, and 2.
)
)
Macros:
SUBTITLE=Contract Programming
DESCRIPTION=The contract programming features of D that help with program correctness.
KEYWORDS=d programming language tutorial book in out dbc