Devil is in the details: final vars and methods

Immutability is good. I have discussed this issue, briefly, in this blog in the past, and it won’t be the last time we discuss it.

Today, we are going to look at how not enforcing immutability can have undesired and unexpected side effects.

Let’s consider this class:

class Developer {
    var baseSalary: Int
    var yearsOfExperience: Int
    
    init(baseSalary: Int, yearsOfExperience: Int) {
        self.baseSalary = baseSalary
        self.yearsOfExperience = yearsOfExperience
    }
}

Now, let’s assume that Human Resources calculates monthly payslips like this:

class HRDepartment {
    func calculatePaySlip(developer: Developer) -> Int {
        return developer.baseSalary + (developer.yearsOfExperience * 2)
    }
}

So, this could happen:

let hr = HRDepartment()
let charles = Developer(baseSalary: 100, yearsOfExperience: 1)

let charlesPayslip = hr.calculatePaySlip(charles)


class HackedDeveloper: Developer {
    override var yearsOfExperience: Int {
        get {
            return 100000000000
        }
        set (newValue) {
            
        }
    }
}

let  = HackedDeveloper(baseSalary: 100, yearsOfExperience: 1)
let hackedCharlesPaySlip = hr.calculatePaySlip()

Boom!

One solution would be making the Developer a struct or a final class. But for the sake of the argument, let’s say that making that class final is not a possibility, because we actually need to subclass it. Which is not a rare use case, by the way. Not everything can always be final, even if it probably should.

In this case, one solution might be declaring those properties as final:

class Developer {
    final var baseSalary: Int
    final var yearsOfExperience: Int
    
    init(baseSalary: Int, yearsOfExperience: Int) {
        self.baseSalary = baseSalary
        self.yearsOfExperience = yearsOfExperience
    }
}

class HRDepartment {
    func calculatePaySlip(developer: Developer) -> Int {
        return developer.baseSalary + (developer.yearsOfExperience * 2)
    }
}

let hr = HRDepartment()
let charles = Developer(baseSalary: 100, yearsOfExperience: 1)

let charlesPayslip = hr.calculatePaySlip(charles)


class HackedDeveloper: Developer {
}

let  = HackedDeveloper(baseSalary: 100, yearsOfExperience: 1)
let hackedCharlesPaySlip = hr.calculatePaySlip()

The point of this post is that the defaults (default access modifiers, default read/write access) are not always the best. And that we need to be aware of what might happen when we rely on the defaults.

And, most importantly, unexpected see effects are avoidable. And we should avoid them.

One Reply to “Devil is in the details: final vars and methods”

  1. Thanks for sharing your knowledge.

    Also the other possibility is to declare the properties like private let.

    private let baseSalary: Int
    private let yearsOfExperience: Int

Leave a Reply to Pat G Cancel reply

Your email address will not be published. Required fields are marked *