DRY
Definition [1]
Maintenance is a constant part of programming. If we have duplicated knowledge within our programs, they all need to be changed in case that knowledge changes (e.g. a new requirement) -> maintenance nightmare.
Definition
Don’t repeat yourself!
Each piece of knowledge must have a single, unambiguous, authoritative representation within a system.
This doesn’t just mean code within a program should not be copy-pasted, but two pieces of code must not duplicate knowledge or intent, even if done in two very different ways.
To test, if your code complies with the principle, see if a single facet of your code need to be changed, does this change require changes at different places in the code as well.
Example [2]
We want to calculate the BMI of five subjects:
1# This example has duplications
2
3# Subject data = [weight_kg, height_m]
4subject1 = [80, 1.62]
5subject2 = [69, 1.53]
6subject3 = [80, 1.66]
7subject4 = [80, 1.79]
8subject5 = [72, 1.60]
9
10bmi_subject1 = int(subject1[0] / subject1[1]**2)
11print("bmi {} = {}".format('subject1', bmi_subject1))
12
13bmi_subject2 = int(subject2[0] / subject2[1]**2)
14print("bmi {} = {}".format('subject2', bmi_subject2))
15
16bmi_subject3 = int(subject3[0] / subject3[1]**2)
17print("bmi {} = {}".format('subject3', bmi_subject3))
18
19bmi_subject4 = int(subject4[0] / subject4[1]**2)
20print("bmi {} = {}".format('subject4', bmi_subject4))
21
22bmi_subject5 = int(subject5[0] / subject5[1]**2)
23print("bmi {} = {}".format('subject5', bmi_subject5))
which produces the following output:
bmi subject1 = 30
bmi subject2 = 29
bmi subject3 = 29
bmi subject4 = 24
bmi subject5 = 28
First let’s remove the duplication of calculating the BMI for various subjects:
1def bmi_calc(weight_kg, height_m):
2 """Calculate BMI from weight in kg and height in meters"""
3 bmi = int(weight_kg / height_m**2)
4 return bmi
5
6# Subject data = [weight_kg, height_m]
7subject1 = [80, 1.62]
8subject2 = [69, 1.53]
9subject3 = [80, 1.66]
10subject4 = [80, 1.79]
11subject5 = [72, 1.60]
12
13bmi_subject1 = bmi_calc(subject1[0], subject1[1])
14print("bmi {} = {}".format('subject1', bmi_subject1))
15
16bmi_subject2 = bmi_calc(subject2[0], subject2[1])
17print("bmi {} = {}".format('subject2', bmi_subject2))
18
19bmi_subject3 = bmi_calc(subject3[0], subject3[1])
20print("bmi {} = {}".format('subject3', bmi_subject3))
21
22bmi_subject4 = bmi_calc(subject4[0], subject4[1])
23print("bmi {} = {}".format('subject4', bmi_subject4))
24
25bmi_subject5 = bmi_calc(subject5[0], subject5[1])
26print("bmi {} = {}".format('subject5', bmi_subject5))
We moved the calculation in its own class, which saves us from explicitly defining the calculation for each subject. If the calculation method changes, there is now only one place (the bmi_calc() method) to adapt the code.
To improve it further, let’s move the print statement to the function as well, since it is duplicated for each subject:
1def bmi_calc(sub_num, weight_kg, height_m):
2 """Calculate BMI from weight in kg and height in meters"""
3 bmi = int(weight_kg / height_m**2)
4 subject = 'subject' + str(sub_num)
5 print("bmi {} = {}".format(subject, bmi))
6
7# Subject data = [weight_kg, height_m]
8subject1 = [80, 1.62]
9subject2 = [69, 1.53]
10subject3 = [80, 1.66]
11subject4 = [80, 1.79]
12subject5 = [72, 1.60]
13
14bmi_subject1 = bmi_calc(1, subject1[0], subject1[1])
15bmi_subject2 = bmi_calc(2, subject2[0], subject2[1])
16bmi_subject3 = bmi_calc(3, subject3[0], subject3[1])
17bmi_subject4 = bmi_calc(4, subject4[0], subject4[1])
18bmi_subject5 = bmi_calc(5, subject5[0], subject5[1])
To improve it even more let’s remove calling each subject individually and move them all to a list and iterate over it:
1def bmi_calc(sub_num, weight_kg, height_m):
2 """Calculate BMI from weight in kg and height in meters"""
3 bmi = int(weight_kg / height_m**2)
4 subject = 'subject' + str(sub_num)
5 print("bmi {} = {}".format(subject, bmi))
6
7# Subject data = [weight_kg, height_m]
8subjects =[[1, 80, 1.62], # subject1
9 [2, 69, 1.53], # subject2
10 [3, 80, 1.66], # subject3
11 [4, 80, 1.79], # subject4
12 [5, 72, 1.60]] # subject5
13
14for sub in subjects:
15 bmi_calc(sub[0], sub[1], sub[2])
Duplication in Documentation
When adding a docstring to a function, don’t explain the function in detail e.g. to compensate for bad design and naming. The code should be readable, so that a docstring must not repeat what is happening in the function.
Duplication in Data
The attributes of a class must not depend on each other in a way, that if one data value changes, another attribute has to be manually adapted.
class Line {
Point start;
Point end;
double length;
}
This is a violation of DRY. The length derives from the start and end attributes.
If the start or the end Point changes, the length must change as well. It’s better to make
length a function:
class Line {
Point start;
Point end;
@property
def double(self):
return self.start.distance_to(self.end);
}