Increasing Vocabulary of a Software
What does it mean to increase the 'vocabulary' of a software? How does refactoring leads to increasing this vocabulary? And how does this increased vocabulary lead to better design?
My all time favorite programing book is Refactoring by Martin Fowler. When I first read it many many years ago, it was the very first chapter that hooked me. Before even introducing the various refactorings, Martin Fowler starts the book by giving a very powerful example of how applying just a few simple cleanups to a code can improve its structure. So, just to have some fun, I wanted to do it myself, but in my own favorite language - Swift. I want to see if the the Swift version is going to be different from Java, maybe even better when we apply idiomatic Swift.
I am going to go in a slightly different order of steps than the book. Additionally, I am going to apply a few of my own refactorings. In this process we are going to learn about this concept of ‘increasing vocabulary’ of a software.
The Starting Program
I converted the program from Java to Swift. There are three main classes - Movie, Rental and Customer.
Movie represents the class which contains its title and pricing code.
class Movie {
static let REGULAR = 0
static let NEW_RELEASE = 1
static let CHILDRENS = 2
private var title: String
private var priceCode: Int
init(title: String, priceCode: Int) {
self.title = title
self.priceCode = priceCode
}
func getPriceCode() -> Int {
return priceCode
}
func setPriceCode(_ code: Int) {
priceCode = code
}
func getTitle() -> String {
return title
}
}
Rental class represents the number of days a movie was rented.
class Rental {
private var movie: Movie
private var daysRented: Int
init(movie: Movie, daysRented: Int) {
self.movie = movie
self.daysRented = daysRented
}
func getDaysRented() -> Int {
return daysRented
}
func getMovie() -> Movie {
return movie
}
}
Customer has the array of all their rentals.
class Customer {
private var name: String
private var rentals: [Rental] = []
init(name: String) {
self.name = name
}
func addRental(_ rental: Rental) {
rentals.append(rental)
}
func getName() -> String {
return name
}
}
There is a method in the Customer class called statement(), which calculates and prints the rental amount and frequent renter points.
func statement() -> String {
var totalAmount: Double = 0
var frequentRenterPoints = 0
var result = "Rental Record for \(name)\n"
for rental in rentals {
var thisAmount: Double = 0
// Determine amounts for each line
switch rental.getMovie().getPriceCode() {
case Movie.REGULAR:
thisAmount += 2
if rental.getDaysRented() > 2 {
thisAmount += Double(rental.getDaysRented() - 2) * 1.5
}
case Movie.NEW_RELEASE:
thisAmount += Double(rental.getDaysRented()) * 3
case Movie.CHILDRENS:
thisAmount += 1.5
if rental.getDaysRented() > 3 {
thisAmount += Double(rental.getDaysRented() - 3) * 1.5
}
default:
break
}
// Add frequent renter points
frequentRenterPoints += 1
if rental.getMovie().getPriceCode() == Movie.NEW_RELEASE &&
rental.getDaysRented() > 1 {
frequentRenterPoints += 1
}
// Show figures for this rental
result += "\t" + rental.getMovie().getTitle() + "\t" +
String(format: "%.1f", thisAmount) + "\n"
totalAmount += thisAmount
}
// Add footer lines
result += "Amount owed is \(String(format: "%.1f", totalAmount))\n"
result += "You earned " + String(frequentRenterPoints) +
" frequent renter points"
return result
}
Step 1: Convert to Struct
Even before I use the first refactoring from book, I know I can convert Movie and Rental to Struct, and clean up a lot of boilerplate code of constructors, getters and setters methods. I can also convert the var to let.
struct Movie {
static let REGULAR = 0
static let NEW_RELEASE = 1
static let CHILDRENS = 2
let title: String
let priceCode: Int
}
struct Rental {
let movie: Movie
let daysRented: Int
}
class Customer {
let name: String
private var rentals: [Rental] = []
init(name: String) {
self.name = name
}
func addRental(_ rental: Rental) {
rentals.append(rental)
}
}
Already the code is cleaned up nicely for the basic objects. 35 lines of boilerplate code is removed. Less code, which does the exact same thing, is always better.
Step 2: Extract Method
The best place to start a refactoring exercise are long methods. They are the ripest places for evolving our thinking on how to better design our software. We take a long method and breaks it up into smaller helper methods. This technique is called Extract Method, and it is the most powerful refactoring technique. In this step we apply this technique to the statement() method.
func statement() -> String {
var totalAmount: Double = 0
var frequentRenterPoints = 0
var result = "Rental Record for \(name)\n"
for rental in rentals {
var thisAmount: Double = 0
// Determine amounts for each line
switch rental.movie.priceCode {
case Movie.REGULAR:
thisAmount += 2
if rental.daysRented > 2 {
thisAmount += Double(rental.daysRented - 2) * 1.5
}
case Movie.NEW_RELEASE:
thisAmount += Double(rental.daysRented) * 3
case Movie.CHILDRENS:
thisAmount += 1.5
if rental.daysRented > 3 {
thisAmount += Double(rental.daysRented - 3) * 1.5
}
default:
break
}
// Add frequent renter points
frequentRenterPoints += 1
if rental.movie.priceCode == Movie.NEW_RELEASE &&
rental.daysRented > 1 {
frequentRenterPoints += 1
}
// Show figures for this rental
result += "\t" + rental.movie.title + "\t" +
String(format: "%.1f", thisAmount) + "\n"
totalAmount += thisAmount
}
// Add footer lines
result += "Amount owed is \(String(format: "%.1f", totalAmount))\n"
result += "You earned " + String(frequentRenterPoints) +
" frequent renter points"
return result
}
We extract the highlight code above to a new method getCharge(for:).
func statement() -> String {
var totalAmount: Double = 0
var frequentRenterPoints = 0
var result = "Rental Record for \(name)\n"
for rental in rentals {
var thisAmount = getCharge(for: rental)
// Add frequent renter points
frequentRenterPoints += 1
if rental.movie.priceCode == Movie.NEW_RELEASE &&
rental.daysRented > 1 {
frequentRenterPoints += 1
}
// Show figures for this rental
result += "\t" + rental.movie.title + "\t" +
String(format: "%.1f", thisAmount) + "\n"
totalAmount += thisAmount
}
// Add footer lines
result += "Amount owed is \(String(format: "%.1f", totalAmount))\n"
result += "You earned " + String(frequentRenterPoints) +
" frequent renter points"
return result
}
private func getCharge(for aRental: Rental) -> Double {
var result: Double = 0
switch aRental.movie.priceCode {
case Movie.REGULAR:
result += 2
if aRental.daysRented > 2 {
result += Double(aRental.daysRented - 2) * 1.5
}
case Movie.NEW_RELEASE:
result += Double(aRental.daysRented) * 3
case Movie.CHILDRENS:
result += 1.5
if aRental.daysRented > 3 {
result += Double(aRental.daysRented - 3) * 1.5
}
default:
break
}
return result
}
One thing I like about Swift is how it lets you choose argument labels which reads like a sentence. In the book, the method was called getChargeFor(Rental aRental). But in Swift, I made it getCharge(for aRental: Rental). Now I call it as:
double thisAmount = getChargeFor(rental) // Java
var thisAmount = getCharge(for: rental) // Swift
The use of argument labels can allow a function to be called in an expressive, sentence-like manner, while still providing a function body that’s readable and clear in intent.
Step 3: Second Extract Method
We can also extract a second method which calculates frequent renter points.
class Customer...
func statement() -> String {
var totalAmount: Double = 0
var frequentRenterPoints = 0
var result = "Rental Record for \(name)\n"
for rental in rentals {
var thisAmount = getCharge(for: rental)
frequentRenterPoints = getFrequentRenterPoints(for: rental)
// Show figures for this rental
result += "\t" + rental.movie.title + "\t" +
String(format: "%.1f", thisAmount) + "\n"
totalAmount += thisAmount
}
// Add footer lines
result += "Amount owed is \(String(format: "%.1f", totalAmount))\n"
result += "You earned " + String(frequentRenterPoints) +
" frequent renter points"
return result
}
func getFrequentRenterPoints(for aRental: Rental) -> Int {
if aRental.movie.priceCode == Movie.NEW_RELEASE
&& aRental.daysRented > 1 {
return 2
}
return 1
}
New Vocabulary Words
As I wrote above, Extract Method is the most powerful of refactoring techniques. Extract Method gives new words to our design vocabulary. For instance, in the above example, we now have two new words - getCharge and getFrequentRenterPoints. These new words are what we are after. They are a way for the existing software to tell us how it wants to evolve. (Yes, software talks if you listen closely!). As we will see in the following steps, once we have these new words, ideas will emerge to design our software better. They are the raw material which will shape the next version of our software’s design. We need to collect, as much as possible, these raw material.
Step 4: Move Method
The question to ask about these new words is - which object should these words belong to? Look at the getCharge and getFrequentRenterPoints methods. These methods do not use any data from the Customer class. All the calculations in these methods are done on aRental. This is a pretty strong indication that these methods should move to Rental.
struct Rental {
var movie: Movie
var daysRented: Int
func getCharge() -> Double {
var result: Double = 0
switch movie.priceCode {
case Movie.REGULAR:
result += 2
if daysRented > 2 {
result += Double(daysRented - 2) * 1.5
}
case Movie.NEW_RELEASE:
result += Double(daysRented) * 3
case Movie.CHILDRENS:
result += 1.5
if daysRented > 3 {
result += Double(daysRented - 3) * 1.5
}
default:
break
}
return result
}
func getFrequentRenterPoints() -> Int {
if movie.priceCode == Movie.NEW_RELEASE && daysRented > 1 {
return 2
}
return 1
}
}
Then the statement() method becomes:
class Customer...
func statement() -> String {
var totalAmount: Double = 0
var frequentRenterPoints = 0
var result = "Rental Record for \(name)\n"
for rental in rentals {
var thisAmount = rental.getCharge()
frequentRenterPoints += rental.getFrequentRenterPoints()
// Show figures for this rental
result += "\t" + rental.movie.title + "\t" +
String(format: "%.1f", thisAmount) + "\n"
totalAmount += thisAmount
}
// Add footer lines
result += "Amount owed is \(String(format: "%.1f", totalAmount))\n"
result += "You earned " + String(frequentRenterPoints) +
" frequent renter points"
return result
}
This has improved the design, however we are not done. We will go about finding more new words.
Step 5: Replace Temp with Query Methods
We have 3 temporary variables: thisAmount, frequentRenterPoints, and totalAmount. Our next step is to remove these temporary variables with methods knows as Query Methods. The idea is same as Extract Method - we are going to get more new words. Our vocabulary will increase. And increased vocabulary leads to better design.
We apply Replace Temp with Query refactoring to remove thisAmount in the statement method.
func statement() -> String {
var totalAmount: Double = 0
var frequentRenterPoints = 0
var result = "Rental Record for \(name)\n"
for rental in rentals {
var thisAmount = rental.getCharge()
frequentRenterPoints += rental.getFrequentRenterPoints()
// Show figures for this rental
result += "\t" + rental.movie.title + "\t" +
String(format: "%.1f", rental.getCharge()) + "\n"
totalAmount += rental.getCharge()
}
// Add footer lines
result += "Amount owed is \(String(format: "%.1f", totalAmount))\n"
result += "You earned " + String(frequentRenterPoints) +
" frequent renter points"
return result
}
That was simple, but we didn’t get any new words. Next, we remove the totalAmount variable.
class Customer...
func statement() -> String {
var totalAmount: Double = 0
var frequentRenterPoints = 0
var result = "Rental Record for \(name)\n"
for rental in rentals {
frequentRenterPoints += rental.getFrequentRenterPoints()
// Show figures for this rental
result += "\t" + rental.movie.title + "\t" +
String(format: "%.1f", rental.getCharge()) + "\n"
totalAmount += rental.getCharge()
}
// Add footer lines
result += "Amount owed is " +
String(format: "%.1f", getTotalCharge()) + "\n"
result += "You earned " + String(frequentRenterPoints) +
" frequent renter points"
return result
}
private func getTotalCharge() -> Double {
rentals.reduce(0) { $0 + $1.getCharge() }
}
We got one new word - getTotalCharge! The getTotalCharge() method uses closure-based reduce() in Swift and using shorthand argument names ($0, $1). This was 7 lines of code in Java using a for-loop, which is now a one liner.
Now we do the same for frequentRenterPoints.
class Customer...
func statement() -> String {
var frequentRenterPoints = 0
var result = "Rental Record for \(name)\n"
for rental in rentals {
frequentRenterPoints += rental.getFrequentRenterPoints()
// Show figures for this rental
result += "\t" + rental.movie.title + "\t" +
String(format: "%.1f", rental.getCharge()) + "\n"
}
// Add footer lines
result += "Amount owed is " +
String(format: "%.1f", getTotalCharge()) + "\n"
result += "You earned " +
String(getTotalFrequentRenterPoints()) +
" frequent renter points"
return result
}
private func getTotalCharge() -> Double {
rentals.reduce(0) { $0 + $1.getCharge() }
}
private func getTotalFrequentRenterPoints() -> Int {
rentals.reduce(0) { $0 + $1.getFrequentRenterPoints() }
}
Step 6: Extension Methods
We have two new words - getTotalCharge and getTotalFrequentRenterPoints. We again ask the question which object should these words belong to? These methods operator on a list of Rental objects. So we can make it an extension method on Rental list, and move these to the Rental file.
class Customer...
func statement() -> String {
var result = "Rental Record for \(name)\n"
for rental in rentals {
// Show figures for this rental
result += "\t" + rental.movie.title + "\t" +
String(format: "%.1f", rental.getCharge()) + "\n"
}
// Add footer lines
result += "Amount owed is " +
String(format: "%.1f", rentals.getTotalCharge()) + "\n"
result += "You earned " +
String(rentals.getTotalFrequentRenterPoints()) +
" frequent renter points"
return result
}
File Rental...
extension Sequence where Element == Rental {
func getTotalCharge() -> Double {
reduce(0) { $0 + $1.getCharge() }
}
func getTotalFrequentRenterPoints() -> Int {
reduce(0) { $0 + $1.getFrequentRenterPoints() }
}
}
I love Swift’s extension methods. These methods are available to any other part of the code that needs them. That helps a lot in coming up with cleaner code for the class.
For now we are done with Customer class and the statement method. The statement method is looking much better than its original state. Now it only calculates the result and leaves the rest of the calculations of totalCharge and totalFrequentRenterPoints to others. This follows the powerful design principle known as ‘Tell, Don’t Ask”.
Tell, Don't Ask principle says that instead of asking an object for its data and then making decisions based on that data, you should tell the object what to do directly. This encourages thinking in terms of actions and responsibilities rather than data and state, leading to better encapsulation and maintainable code.
Step 7: Move Method
Lets move our attention to the Renter class, and its two new methods getCharge and getFrequentRenterPoints. Are these methods in the right place?
The way to find out where a particular method should live, we should look at what data does it operator on. These methods use two pieces of data - the daysRented and movie type. One belongs to Rental and other to the Movie structs. Its 50/50. So where should these methods live?
We choose Movie. Why? The book explains it brilliantly.
It’s about “minimizing change”. Think about which of these two data - daysRental or movie type - is more likely to change, that will lead to lot of other code changing? It will be the movie type. When we add new movie types, the switch and the if statements will change. If these methods live in Rental, then adding a new movie type will lead to two class changing - Movie and Rental. But if these methods live in Movie, then only one class will need to change, the Movie itself. Nobody else will need to know. Better encapsulation leads to another design principle called the “Open-Closed Principle”.
According to open-closed principle, the code should be open to extension (adding new movie types) but closed for modification (a big ripple effect across all the classes).
Lets move getCharge and getFrequentRenterPoints methods from Rental struct to Movie struct.
struct Rental...
func getCharge() -> Double {
return movie.getCharge(for: daysRented)
}
func getFrequentRenterPoints() -> Int {
return movie.getFrequentRenterPoints(for: daysRented)
}
struct Movie...
func getCharge(for daysRented: Int) -> Double {
var result: Double = 0
switch priceCode {
case Movie.REGULAR:
result += 2
if daysRented > 2 {
result += Double(daysRented - 2) * 1.5
}
case Movie.NEW_RELEASE:
result += Double(daysRented) * 3
case Movie.CHILDRENS:
result += 1.5
if daysRented > 3 {
result += Double(daysRented - 3) * 1.5
}
default:
break
}
return result
}
func getFrequentRenterPoints(for daysRented: Int) -> Int {
if priceCode == Movie.NEW_RELEASE && daysRented > 1 {
return 2
}
return 1
}
This is the brilliant insight right here! This is the inflection point from where the remainder of the refactoring will happen. This is where we start reaping the benefits of our new vocabulary that will lead us to a better polymorphic solution.
Step 8: Inheritance, Composition or Enums?
We are going to move our attention to the Movie struct. Its got three types, and based on what type of the movie is, we calculate the charge and frequent renter points.
struct Movie {
static let REGULAR = 0
static let NEW_RELEASE = 1
static let CHILDRENS = 2
let title: String
let priceCode: Int
func getCharge(for daysRented: Int) -> Double {
var result: Double = 0
switch priceCode {
case Movie.REGULAR:
result += 2
if daysRented > 2 {
result += Double(daysRented - 2) * 1.5
}
case Movie.NEW_RELEASE:
result += Double(daysRented) * 3
case Movie.CHILDRENS:
result += 1.5
if daysRented > 3 {
result += Double(daysRented - 3) * 1.5
}
default:
break
}
return result
}
func getFrequentRenterPoints(for daysRented: Int) -> Int {
if priceCode == Movie.NEW_RELEASE && daysRented > 1 {
return 2
}
return 1
}
}
There are 3 options to move forward. First, we can use inheritance, and create sub classes for Movie. But notice that switch statement and the if statement is operating on the priceCode. These are not movie types but priceCode types!
Next option is we can use composition, and create an interface or abstract class Price, which will have the 3 sub-type implementations - RegularPrice, NewReleasePrice and ChildrenPrice. The Movie struct will have reference to the Price type, which it can polymorphically use to get charge and frequent renter points.
The book uses this option 2. But I am doing to defer slightly from the book. We have a third option. Instead of making Price an abstract class with various types being subclasses, I am going to make it an Enum. This is more idiomatic way of doing Swift. Swift Enums are too powerful, and in almost all cases, removes the need to create interfaces or abstract classes.
enum PriceCode {
case childrens, regular, newRelease
}
Now we can move getCharge() and getFrequentRenterPoints() to PriceCode.
struct Movie {
static let REGULAR = 0
static let NEW_RELEASE = 1
static let CHILDRENS = 2
let title: String
let priceCode: PriceCode
func getCharge(for daysRented: Int) -> Double {
return priceCode.getCharge(for: daysRented)
}
func getFrequentRenterPoints(for daysRented: Int) -> Int {
return priceCode.getFrequentRenterPoints(for: daysRented)
}
}
enum Price...
func getCharge(for daysRented: Int) -> Double {
switch self {
case .regular:
var charge = 2.0
if daysRented > 2 {
charge += Double(daysRented - 2) * 1.5
}
return charge
case .newRelease:
return Double(daysRented) * 3
case .childrens:
var charge = 1.5
if daysRented > 3 {
charge += Double(daysRented - 3) * 1.5
}
return charge
}
}
func getFrequentRenterPoints(for daysRented: Int) -> Int {
switch self {
case .newRelease: return daysRented > 1 ? 2 : 1
default: return 1
}
}
In Java example, they had it as classes, so the static movie types were needed. But in Swift, we can just use the enums directly. There is no difference.
Enums over Polymorphism
We could have done a polymorphic approach in Swift using Protocols.
protocol Price {
func getCharge(for daysRented: Int) -> Double
func getFrequentRenterPoints(for daysRented: Int) -> Int
}
Then had 3 structs adopt this protocol.
struct RegularPrice: Price { ... }
struct NewReleasePrice: Price { ... }
struct ChildrensPrice: Price { ... }
Movie will have reference to Price protocol.
struct Movie...
let priceCode: any Price
}
Here the priceCode is of box type Price, which allows the polymorphic behavior. Setting aside the performance cost of box types due to dynamic dispatch, I don’t like the proliferation of these small small structs every time we add a new type of price code. Enums allows keeping all logic in one place in a switch statement. Yes, the switch statement will grow as we add new price types, but they will stay in one place in one file. Enums also have different associative values for its case values. That is like different subclasses with different constructor fields.
Therefore, I generally prefer enums over polymorphism.
Step 9: PriceCode on Movie or Rental?
The example in the book end with step 8 above. But I want go on and do a couple more things.
Should Movie have a price code or Rental? I think Rental should have the price code. A movie’s release date or genre can influence its price at time of its rental. Few months after its release, a NEW_RELEASE price code will turn into REGULAR price code. But the Movie object shouldn’t change. Furthermore, there is a big hint in the code - for Rental to get a charge or frequent renter points, it has to make two hops, through Movie and PriceCode. But Movie has no use of price, except to quickly go and delegate to PriceCode. It doesn’t even pass any of its own data to PriceCode, like Rental passes daysRented.
So lets move priceCode to Rental.
struct Rental {
var movie: Movie
var priceCode: PriceCode
var daysRented: Int
func getCharge() -> Double {
return priceCode.getCharge(for: daysRented)
}
func getFrequentRenterPoints() -> Int {
return priceCode.getFrequentRenterPoints(for: daysRented)
}
}
Now PriceCode can be created at the time of Rental, without changing Movie.
// In 2010
var rental = Rental(movie: inception, priceCode: Price.newRelease)
// In 2025
var rental = Rental(movie: inception, priceCode: Price.regular)
Movie just remains with a title. More needless code is deleted.
struct Movie {
let title: String
let priceCode: PriceCode
func getCharge(for daysRented: Int) -> Double {
return priceCode.getCharge(for: daysRented)
}
func getFrequentRenterPoints(for daysRented: Int) -> Int {
return priceCode.getFrequentRenterPoints(for: daysRented)
}
}
We could also delete the whole Movie struct and move the title to Rental. However, I think we should leave it. Later, if needed, we can add genre and releaseDate to Movie struct, which will help Rental to assign a price code.
struct Movie {
let title: String
let genre: String
let releaseDate: Date
}
Step 10: Naming
This is the final step. We are going to rename a few methods. It is so important to name things correctly.
Methods should communicate both what it’s doing and how it should be interpreted by the caller.
We have two methods getCharge and getFrequentRenterPoints that I am going to rename.
On PriceCode, the results of these methods involves computation based on input or internal state. The name should communicate cost or intentionality (e.g., not just fetching a stored value). We want to signal to caller: “this isn’t a stored property, it’s logic.” Therefore, I think a more appropriate names are calculateCharge() and calculateFrequentRenterPoints().
enum Price {
case childrens, regular, newRelease
func calculateCharge(for daysRented: Int) -> Double {
...calculations...
}
func calculateFrequentRenterPoints(for daysRented: Int) -> Int {
...calculations...
}
}
However, on Rental, these methods are very simple. When it comes to query methods (methods that retrieve information and don’t cause side effects), the Swift API Design Guidelines is to avoid using “get” as a prefix.
“Omit needless words.” Methods and properties should read as grammatical English phrases. Avoid get, is, do, compute, etc., unless they’re necessary for clarity or disambiguation.
So we can name these methods simply as charge() and frequentRenterPoints(). Further, for simple side-effect-free queries, Swift encourages to use Computed Properties. Lets do this for getTotalCharge() and getTotalFrequentRenterPoints() extension methods as well.
struct Rental...
var charge: Double {
return priceCode.calculateCharge(for: daysRented)
}
var frequentRenterPoints: Int {
return priceCode.calculateFrequentRenterPoints(for: daysRented)
}
extension Sequence where Element == Rental {
var totalCharge: Double {
reduce(0) { $0 + $1.charge }
}
var totalFrequentRenterPoints: Int {
reduce(0) { $0 + $1.frequentRenterPoints }
}
}
And the statement() method becomes even cleaner.
class Customer...
func statement() -> String {
var result = "Rental Record for \(name)\n"
for rental in rentals {
result += "\t" + rental.movie.title + "\t" +
String(format: "%.1f", rental.charge) + "\n"
}
// Add footer lines
result += "Amount owed is " +
String(format: "%.1f", rentals.totalCharge) + "\n"
result += "You earned " +
String(rentals.totalFrequentRenterPoints) +
" frequent renter points"
return result
}
Thats it! We are done.
Conclusion
Lets summarize the insights we gained from this exercise.
When we first write our software, these new “vocabulary” words do not exist. In our example, in the beginning this software’s purpose was to print out a customer’s rental statement with charges based on “days rented”. Life was simple. Over time, new features are added, like in this case we must have added new movie types (“new releases” and “children”) and frequent renter points. But we kept shoving them into the statement method. So the vocabulary of this program remained stagnant. With refractoring, we discovered new words - charges and frequentRenterPoints. Those led to the creation of a new enum PriceCode. This significantly improved the design of our software. Now, if we add a new price code, it would just need modifying one Enum. In this simple example its hard to see the cost savings, but imagine a code base with thousands of line of code.
We also looked at few design principles - Tell, Don’t Ask, Open-Closed principle, Enums over Polymorphism, and Naming guidelines.
Lastly, I hope I was able to illustrate some of the lovely features of the Swift programming language - Structs, Enums, Argument labels, Extension Methods, Computed Properties, and String Interpolation.
Thanks for reading!