All the fields and methods should be private to start with.
In Swift, they are three levels of access levels
- private
- internal
- public
Private and public are obvious. Internal access means public access for the current module, but private for everything else.
The default is the internal access, which means public if we are working with just one module.
But, of course, that is not a good idea. Every class should try to hide as much as it can from the other classes.
One way to achieve this is to make everything private to start with and only change it if need be.
Here is the class used in the previous post:
class Account {
var isCheckingAccount: Bool = false
func isChecking() -> Bool {
return isCheckingAccount
}
}
We notice that isCheckingAccount is never accessed directly. So we can go ahead and make it private:
class Account {
private var isCheckingAccount: Bool = false
func isChecking() -> Bool {
return isCheckingAccount
}
}
Wednesday, March 16, 2016
Tuesday, March 15, 2016
Replace subclasses with fields
The subclasses need to have a reason for existence. A very good reason.
When that reason disappears or becomes slim, it might be time for removing the subclass.
Let's consider the following hierarchy of classes:
class Account {
func isChecking() -> Bool {
return false
}
}
class CheckingAccount: Account {
override func isChecking() -> Bool {
return true
}
}
class SavingAccount: Account {
override func isChecking() -> Bool {
return false
}
}
While at the beginning the CheckingAccount and SavingAccount were different in functionality, in time, the only difference between them remained just the isChecking method that returns true for one and false for another.
We can refactor this structure, by adding one property to the Account class to reflect if the account is checking or not.
class Account {
var isCheckingAccount: Bool = false
func isChecking() -> Bool {
return isCheckingAccount
}
}
Monday, March 14, 2016
Replace parameter with method
Apply this method when the code is invoking one function and passes the result to another function.
If the second function can invoke the function by itself, it should.
Let's consider the following class:
class User {}
And a function that retrieves the current user:
func getCurrentUser() -> User {
var currentUser: User
//find the user
return currentUser
}
The following function returns all the friends of a user:
func findFriendsForCurrentUser(user:User) -> Array<User> {
var array = [User]()
//uses user to find his friends
return array
}
We can invoke the functionality as follows:
let currentUser = User()
let currentUserFriends = findFriendsForCurrentUser(currentUser)
We notice though, that we always use the functionality for the current user.
So there is no point in passing the current user, when the function can get it by itself.
We remove the parameter and change the function to get the current user by itself:
func findFriendsForCurrentUser() -> Array<User> {
let user = getCurrentUser()
var array = [User]()
//uses user to find his friends
return array
}
If the second function can invoke the function by itself, it should.
Let's consider the following class:
class User {}
And a function that retrieves the current user:
func getCurrentUser() -> User {
var currentUser: User
//find the user
return currentUser
}
The following function returns all the friends of a user:
func findFriendsForCurrentUser(user:User) -> Array<User> {
var array = [User]()
//uses user to find his friends
return array
}
We can invoke the functionality as follows:
let currentUser = User()
let currentUserFriends = findFriendsForCurrentUser(currentUser)
We notice though, that we always use the functionality for the current user.
So there is no point in passing the current user, when the function can get it by itself.
We remove the parameter and change the function to get the current user by itself:
func findFriendsForCurrentUser() -> Array<User> {
let user = getCurrentUser()
var array = [User]()
//uses user to find his friends
return array
}
Friday, March 11, 2016
Rename method
When to rename a method or a function?
But before that, do you know what is the difference between a method and a function?
Well a method is a married function :-)
Joking, a method is a function that is associated (for life) with another class,. So a method is a class function.
Back to our refactoring.
We need to rename a function in one of the following cases:
- the previous developer picked a name so bad that nobody understand what it is about. Remember the name should tell us everything we need to know about its purpose
- the purpose of the function changed
- the return type changed. For example if getUser is now returning an Account, of course it needs to be changed to getAccount
- the name is not following the coding standards the team adopted.
Finding a good name is not easy. But working with bad names is a pain, so the effort is paying off.
Read more here How to name your variables and your kids
But before that, do you know what is the difference between a method and a function?
Well a method is a married function :-)
Joking, a method is a function that is associated (for life) with another class,. So a method is a class function.
Back to our refactoring.
We need to rename a function in one of the following cases:
- the previous developer picked a name so bad that nobody understand what it is about. Remember the name should tell us everything we need to know about its purpose
- the purpose of the function changed
- the return type changed. For example if getUser is now returning an Account, of course it needs to be changed to getAccount
- the name is not following the coding standards the team adopted.
Finding a good name is not easy. But working with bad names is a pain, so the effort is paying off.
Read more here How to name your variables and your kids
Thursday, March 10, 2016
Refactoring - Remove the paramenter
This refactoring is so simple, we'll not even give an example to prove it.
With functionality change, some of the parameters of a function become obsolete and not used.
When that is the case, of course, they need to be removed.
Easier said than done in some cases, especially when the functions are part of a library used by other parties.
In that case, we can keep the function with the extra non used parameter, deprecated it, and create another function with the correct parameters.
At least the new users of the function will know to use the correct version.
Let's assume the following function:
func testFunction(param1:Int, param2: Int, unusedParam: Int) {
}
@available(*, deprecated=1.0, message="unusedParam is not used anymore")
func testFunction(param1:Int, param2: Int, unusedParam: Int) {
testFunction(param1, param2: param2)
}
func testFunction(param1:Int, param2: Int) {
//move here the code from the previous function
}
With functionality change, some of the parameters of a function become obsolete and not used.
When that is the case, of course, they need to be removed.
Easier said than done in some cases, especially when the functions are part of a library used by other parties.
In that case, we can keep the function with the extra non used parameter, deprecated it, and create another function with the correct parameters.
At least the new users of the function will know to use the correct version.
Let's assume the following function:
func testFunction(param1:Int, param2: Int, unusedParam: Int) {
}
After few iterations, unusedParam is not used anymore. We can remove it right away, but this will break few other programs that are using our function.
So instead of doing that, we can mark it as deprecated and create a new, refactored version as follows:
@available(*, deprecated=1.0, message="unusedParam is not used anymore")
func testFunction(param1:Int, param2: Int, unusedParam: Int) {
testFunction(param1, param2: param2)
}
func testFunction(param1:Int, param2: Int) {
//move here the code from the previous function
}
Wednesday, March 9, 2016
Refactoring: remove the named parameters
The functions in Swift can have named or unnamed parameters. Usually, named parameters give more clarity on their purpose. Sometime though, the names are redundant. In those cases, the names can be removed.
For example, in the following cases:
func max(firstValue firstValue:Float, secondValue:Float) -> Float {
if firstValue < secondValue {
return secondValue
} else {
return firstValue
}
}
func powerOfTwo(power power:Int) -> Float {
if power < 0 {
return 1 / powerOfTwo(power: -power)
}
if power == 0 {
return 1
}
return 2 * powerOfTwo(power: power-1)
}
We can invoke the functions as follows:
max(firstValue:10, secondValue: 20)
powerOfTwo(power: 10)
The parameters' names are not bringing any value to the code. It is obvious that the first one is calculating the maximum between two numbers. The order of the numbers does not matter.
The second one is calculating the power of two. The parameter is the power and that is obvious.
We can remove the parameter names, making the code simpler:
func max(firstValue:Float, _ secondValue:Float) -> Float {
if firstValue < secondValue {
return secondValue
} else {
return firstValue
}
}
func powerOfTwo(power:Int) -> Float {
if power < 0 {
return 1 / powerOfTwo(-power)
}
if power == 0 {
return 1
}
return 2 * powerOfTwo(power-1)
}
In this case, we can call the functions as follows:
max(10, 20)
powerOfTwo(10)
For example, in the following cases:
func max(firstValue firstValue:Float, secondValue:Float) -> Float {
if firstValue < secondValue {
return secondValue
} else {
return firstValue
}
}
func powerOfTwo(power power:Int) -> Float {
if power < 0 {
return 1 / powerOfTwo(power: -power)
}
if power == 0 {
return 1
}
return 2 * powerOfTwo(power: power-1)
}
We can invoke the functions as follows:
max(firstValue:10, secondValue: 20)
The parameters' names are not bringing any value to the code. It is obvious that the first one is calculating the maximum between two numbers. The order of the numbers does not matter.
The second one is calculating the power of two. The parameter is the power and that is obvious.
We can remove the parameter names, making the code simpler:
func max(firstValue:Float, _ secondValue:Float) -> Float {
if firstValue < secondValue {
return secondValue
} else {
return firstValue
}
}
func powerOfTwo(power:Int) -> Float {
if power < 0 {
return 1 / powerOfTwo(-power)
}
if power == 0 {
return 1
}
return 2 * powerOfTwo(power-1)
}
In this case, we can call the functions as follows:
max(10, 20)
powerOfTwo(10)
Tuesday, March 8, 2016
Refactoring - remove control flag
Many times when there are a series of conditional tests, we use a flag to keep a temporary boolean value.
If this is happening inside a function we can replace this flag with return or exit statement.
Have a look at the following example:
class User {
var firstName = ""
var middleName = ""
var lastName = ""
}
We also have a database of users:
var userArray = [User]()
And here is a search function for a user that has a specific name:
func searchName(name:String) -> User? {
var found = false
var user:User?
for var index = 0; (index < userArray.count) && !found; ++index {
let crtUser = userArray[index]
if crtUser.firstName == name {
user = crtUser
found = true
}
if crtUser.middleName == name {
user = crtUser
found = true
}
if crtUser.lastName == name {
user = crtUser
found = true
}
}
return user
}
So many lines for a simple search.
We can eliminate the variable and instead of storing locally the user, we can just return it as follows:
func searchName(name:String) -> User? {
for var index = 0; index < userArray.count; ++index {
let crtUser = userArray[index]
if crtUser.firstName == name {
return crtUser
}
if crtUser.middleName == name {
return crtUser
}
if crtUser.lastName == name {
return crtUser
}
}
return nil
}
We can continue the refactoring by combining all the tests in one:
func searchName(name:String) -> User? {
for var index = 0; index < userArray.count; ++index {
let crtUser = userArray[index]
if (crtUser.firstName == name) || (crtUser.middleName == name) || (crtUser.lastName == name) {
return crtUser
}
}
return nil
}
Also we notice that a for-in might work better and the code look more elegant:
func searchName(name:String) -> User? {
for crtUser in userArray {
if (crtUser.firstName == name) || (crtUser.middleName == name) || (crtUser.lastName == name) {
return crtUser
}
}
return nil
}
If this is happening inside a function we can replace this flag with return or exit statement.
Have a look at the following example:
class User {
var firstName = ""
var middleName = ""
var lastName = ""
}
We also have a database of users:
var userArray = [User]()
And here is a search function for a user that has a specific name:
func searchName(name:String) -> User? {
var found = false
var user:User?
for var index = 0; (index < userArray.count) && !found; ++index {
let crtUser = userArray[index]
if crtUser.firstName == name {
user = crtUser
found = true
}
if crtUser.middleName == name {
user = crtUser
found = true
}
if crtUser.lastName == name {
user = crtUser
found = true
}
}
return user
}
So many lines for a simple search.
We can eliminate the variable and instead of storing locally the user, we can just return it as follows:
func searchName(name:String) -> User? {
for var index = 0; index < userArray.count; ++index {
let crtUser = userArray[index]
if crtUser.firstName == name {
return crtUser
}
if crtUser.middleName == name {
return crtUser
}
if crtUser.lastName == name {
return crtUser
}
}
return nil
}
We can continue the refactoring by combining all the tests in one:
func searchName(name:String) -> User? {
for var index = 0; index < userArray.count; ++index {
let crtUser = userArray[index]
if (crtUser.firstName == name) || (crtUser.middleName == name) || (crtUser.lastName == name) {
return crtUser
}
}
return nil
}
Also we notice that a for-in might work better and the code look more elegant:
func searchName(name:String) -> User? {
for crtUser in userArray {
if (crtUser.firstName == name) || (crtUser.middleName == name) || (crtUser.lastName == name) {
return crtUser
}
}
return nil
}
Subscribe to:
Posts (Atom)
