PS2: How to rename things?
Conflict resolution
You have not yet learned how to resolve merge conflicts (divergent branches) in Git, which occur when there are multiple versions of the same file in different repositories and you attempt to synchronize them.
If you encounter difficulties, call your instructor over.
Once this skill is mastered, you will be on your own in managing conflicts.
Objectives¶
The goal of this session is to understand the following points:
- Working with Git
- Magic numbers
- User input
- Members of a C++ class
- Getter and setter
- Constructor of a class
- Different naming conventions
- Intention-revealing names
Exercise 1 : Magic numbers¶
#include <iostream>
#include <string>
using namespace std;
bool qualifiesForDiscount(double originalPrice) {
return originalPrice >= 10;
}
double calculateDiscountPercentage(double originalPrice, bool isStudent, bool isLoyalCustomer) {
double discountPercentage = 0.0;
if (qualifiesForDiscount(originalPrice)) {
if (isStudent) {
discountPercentage += 10;
}
if (isLoyalCustomer) {
discountPercentage += 10;
}
}
return discountPercentage;
}
double calculateDiscountAmount(double originalPrice, double discountPercentage) {
return originalPrice * discountPercentage/100;
}
double calculateFinalPrice(double originalPrice, bool isStudent, bool isLoyalCustomer){
double discountPercentage = calculateDiscountPercentage(originalPrice, isStudent, isLoyalCustomer);
double discountAmount = calculateDiscountAmount(originalPrice, discountPercentage);
double finalPrice = originalPrice - discountAmount;
return finalPrice;
}
double redeemStudentPoints(double discountAmount){
return discountAmount*(100-10)/100;
}
double redeemLoyaltyPoints(double discountAmount){
return discountAmount*(100-2*10)/100;
}
double calculateRewardPoints(double discountAmount, bool isStudent, bool isLoyalCustomer){
double rewardPoints = 0.0;
if (isStudent){
rewardPoints = redeemStudentPoints(discountAmount);
} else if (isLoyalCustomer){
rewardPoints = redeemLoyaltyPoints(discountAmount);
}
return rewardPoints;
}
int main() {
const double originalPrice = 100;
const bool isStudent = true;
const bool isLoyalCustomer = true;
cout << "Original price: " << originalPrice << " euros." << endl;
double finalPrice = calculateFinalPrice(originalPrice,isStudent,isLoyalCustomer);
cout << "Final price with student and loyalty discounts: " << finalPrice << " euros." << endl;
double discountAmount = originalPrice - finalPrice;
cout << "Your reward points: " << calculateRewardPoints(discountAmount,isStudent,isLoyalCustomer) << endl;
return 0;
}
- Save and compile the code (with
g++ -o magic-numbers magic-numbers.cpp
).
Run
./magic-numbers
.Do you understand what this code does?
Hints
A customer buys a product at the price of originalPrice
.
They can get discounts if they are a student or a loyal customer, and the discounts also allow them to earn reward points.
The rules are as follows:
- A discount only applies if
originalPrice
is greater than 10 euros. - A student gets a 10% discount.
- A loyal customer also gets a 10% discount.
- If the customer is both a student and loyal, the discounts adds up to a total of 20%.
- With
originalPrice = 100
euros, the total discount is 20 euros. - Reward points are calculated by applying a percentage to this discount:
- (which gives 90% here) gives 18 points.
- (which gives 80% here) gives 16 points.
- Since the student discount is always more advantageous, it is applied first.
- Thus, in this example, the customer gets 18 points.
- Although the names of the elements are appropriate, this code is not clean. Can you spot the reasons?
Magic numbers
Magic numbers refer to numbers that are used directly in the code.
Often, these numbers appear without a clear justification.
In this example, although the meaning of some numbers can be inferred from the other well-named elements, it is better to avoid them.
Some magic numbers may be acceptable, such as 100
for percentages, 0
for initializing a variable, or 1
for an increment.
However, the majority of them should be avoided.
Here, the number 10
appears multiple times, but its meaning varies depending on the context. Imagine that we wanted to modify the eligibility threshold for a discount, adjust the discount percentages, or change the reward formulas. The risk of errors would then become significant. Additionally, it is difficult to identify and modify the correct values without having to dive into the entire code to understand its logic.
The solution is to assign explicit names to these magic numbers based on their scope, whether they are local or global.
It is essential to follow the proper naming conventions covered in the course to avoid magic numbers.
- Name the magic numbers.
Have you completed your task satisfactorily?
A simple way to check if you’ve applied the naming conventions correctly is to try modifying a single parameter at a time (for example, the student discount percentage, customer discount, etc.).
- Would you have been able to make this change in a similar 10,000-line code using a search (Ctrl + F)?
- Were you able to modify a single variable (just one line of code)?
If not, rename them.
User input¶
To avoid recompiling the code every time you modify originalPrice
, isStudent
, and isLoyalCustomer
, we can retrieve the values from the keyboard during execution.
- Replace the following lines with the code below.
const double originalPrice = 100;
const bool isStudent = true;
const bool isLoyalCustomer = true;
double userOriginalPrice;
cout << "Enter the original price: ";
cin >> userOriginalPrice;
const double& originalPrice = userOriginalPrice;
bool userIsStudent;
cout << "Are you a student? (1:Yes, 0:No) ";
cin >> userIsStudent;
const bool& isStudent = userIsStudent;
bool userIsLoyalCustomer;
cout << "Do you have a loyalty card? (1:Yes, 0:No) ";
cin >> userIsLoyalCustomer;
const bool& isLoyalCustomer = userIsLoyalCustomer;
const
const
Using const <type>&
for user inputs ensures that these values (which remain constant throughout the execution) cannot be accidentally modified in the code, thus preventing unexpected errors.
The reference (&
) avoids making copies of variable values, which is especially important when working with objects that consume a lot of memory.
Here, it is not necessary to use a reference since double
and bool
are small types and have negligible memory overhead.
This is mainly to provide an example using &
.
In general, using const
in front of immutable variables makes the code clearer by explicitly indicating that these values should not be modified, which makes the program easier to maintain and understand.
Have you correctly used const
in your code ?
- Test your code and answer the questions in the Quiz.
Git
Have you remembered to maintain your repositories?
Exercise 2 : Classes and methods¶
#include <iostream>
using namespace std;
double b1 = 0.0;
double b2 = 100.0;
void d1(double x) {
b1 += x;
}
void d2(double x) {
b1 += x;
}
void w1(double x) {
if (x + 4 <= b1) {
b1 -= (x + 4);
} else {
cout << "Insufficient funds!" << endl;
}
}
void w2(double x) {
if (x + 2 <= b2) {
b2 -= (x + 2);
} else {
cout << "Insufficient funds!" << endl;
}
}
int main() {
d1(100.0);
w1(50.0);
w2(50.0);
cout << "First account balance: " << b1 << endl;
cout << "Second account balance: " << b2 << endl;
return 0;
}
- Do you understand what this code does?
Hints
- There are two accounts with two different types: type
1
, which we will call “Basic”, and type2
, called “Standard”. - The first account
b1
has an initial balance of0.0
euros, while the second accountb2
has an initial balance of100.0
euros. - You can deposit an amount
x
into each account, respectively viad1
for the first account andd2
for the second. - Money can be withdrawn with
w1
for the first account andw2
for the second. - When withdrawing, the account of type
1
(Basic) applies a withdrawal fee of 4 euros, while the account of type2
(Standard) applies a fee of 2 euros.
Quiz: Which of the two accounts has the higher balance after these transactions?
Replace the previous transactions with the following:
d1(100.0);
w1(50.0);
d2(100.0);
w2(50.0);
- Quiz: Now, which account has the higher balance?
Have you spotted the bug?
- Is the code logical?
- What is the source of the accounting error?
- Is it easy to add and perform transactions with a third account?
- What improvements can be made to this code?
- To improve this code, we will rewrite it as follows:
#include <iostream>
using namespace std;
enum t {B, S};
class b {
private:
double mB1;
t mT;
double mF;
public:
b(double b1, t t1) : mB1(b1), mT(t1) {
switch(t1){
case B:
mF = 4;
break;
case S:
mF = 2;
break;
}
}
void d(double x) {
mB1 += x;
}
void w(double x) {
if (x + mF <= mB1) {
mB1 -= (x + mF);
} else {
cout << "Insufficient funds!" << endl;
}
}
double getB1() const {
return mB1;
}
string tToString() const {
switch(mT){
case B: return "Basic";
case S: return "Standard";
default: return "Unknown";
}
}
};
int main() {
b a1(0.0,B);
b a2(100.0,S);
a1.d(100.0);
a1.w(50.0);
a2.d(100.0);
a2.w(50.0);
cout << "First account type: " << a1.tToString() << " and balance: " << a1.getB1() << endl;
cout << "Second account type: " << a2.tToString() << " and balance: " << a2.getB1() << endl;
return 0;
}
const
method in a class
const
method in a classA const
method is a member function of a class that, by placing the const
keyword at the end of its declaration, guarantees that it does not modify the attributes of the object. This particularly applies to getters, which are often defined as follows:
<attributeType> getAttribute() const {
return mAttribute;
}
- Do you understand what this code does?
Hints
- The
t
object of typeenum
can take the valuesB
(for “Basic”) orS
(for “Standard”). - The
b
class represents a bank account. - It has the following attributes:
mB1
, which represents its balance.mT
, which indicates its type (Basic or Standard).mF
, which is its withdrawal fee.
The following code is the constructor of the class:
b(double b1, t t1) : mB1(b1), mT(t1) {
switch(t1){
case B:
mF = 4;
break;
case S:
mF = 2;
break;
}
}
- An object of class
b
is constructed with two arguments:double b1
, which is its initial balance, andt t1
, which defines the account type. - The
switch(t1)
manages the different account types.- If
t1 == B
, the withdrawal fee is 4 euros. - If
t1 == S
, the withdrawal fee is 2 euros.
- If
- The
tToString()
method displays the types as corresponding strings.
Quiz: This time, which account has the highest balance?
This code compiles and works as it should, but it is a real eyesore. It hurts me physically to look at it. It is essential to rename these horrors.
Did you complete your task?
- Did you correctly apply the camelCase convention?
- Did you follow the naming conventions for classes and methods? (The names of
enum
objects should also start with a capital letter.) - Did you follow the conventions for using the
m
(or_
) prefix and for getters and setters? - Are there still variables, methods, or objects with names that consist of a single letter?
- Do meaningless numbers still appear in the names?
- Are the names you chose sufficiently descriptive of the intent behind each variable, method, or object?
If you have any doubts, feel free to refer back to the course.
If you think you’ve correctly renamed the elements, remember that a good programmer should be able to write the same code correctly from the start. Of course, one can always go back to improve the code, but the level of code presented in this exercise is simply unacceptable. By the end of this course, you should be able to name elements properly from the start. In fact, I spent more time making this code functional but visually disastrous than writing it correctly from the start. Inconsistent and vague names make the code hard to understand and call its logic into question.
I hope this exercise has convinced you of the importance of clean coding 😊.
Recompile and test your code.
Add a third “Premium” type with a withdrawal fee of 1 euro.
In the
int main()
function, replace the existing code with the following instructions:
- Create three accounts with the three different types (Basic, Standard, and Premium), each having an initial balance of 100.0 euros.
- Make a deposit of 50 euros into each account.
- Make a withdrawal of 148 euros from each account.
- Display the information (types and balances) of the three accounts.
- Quiz: What is the sum of the balances of all three accounts?
Return to the objectives and check the points you have mastered. Review the points you have not fully understood yet. Ask your instructor for help if needed.
Git
At the end of this assignment, your remote repository should be structured as follows:
- At the root, you will find the directories
PS1/
andPS2/
, along with the files.gitignore
andREADME.md
. You can also see the configuration directory.git
if you are displaying hidden directories. - The
PS1/
directory should contain the fileshello-world.cpp
andmy-first-file.txt
. - The
PS2/
directory should contain the filesclasses-and-methods.cpp
andmagic-numbers.cpp
.
Your working directory may contain ignored files, but make sure to check that your GitLab repository is clean, as it will be evaluated at the end of the course!
If this is not the case, go back to the end of PS1, where additional instructions have been provided to help you clean your remote repository.