This content originally appeared on Level Up Coding - Medium and was authored by Anant
During all the years of my development career and probably your’s too. if you are not even a developer but involved in any kind of Software development process you would have heard this word many times. It is quite easy to use this word but difficult to adopt it in your daily practice as Developer(s) and detect these code smells. In this article I will describe some of the signs that can make you equipped with a strong code smell sensing nose.
What is Code Smell ?
Code Smell refers to certain characteristics or patterns in the code that are potential problems (What kind of problems i’ll come on that soon.) or indicate that the code can be improved. It doesn't necessarily mean that there is a bug, it suggests that there is some room to enhance the code’s readability, flexibility, performance and maintainability. Lots of definitions, now take a look at some examples that will actually provide some real context into the concept of Code Smell.
- Longer Methods or functions :
- A long method with excessive lines of code can make the code difficult to understand and maintain.
Bad example : 👇
fun calculateTotalPrice(items: List<Item>): Double {
var totalPrice = 0.0
for (item in items) {
if (item.isAvailable && item.price > 0) {
if (item.discountPercentage > 0) {
val discountedPrice = item.price - (item.price * item.discountPercentage / 100)
if (item.isTaxable) {
val taxAmount = discountedPrice * TAX_RATE
totalPrice += discountedPrice + taxAmount
} else {
totalPrice += discountedPrice
}
} else {
if (item.isTaxable) {
val taxAmount = item.price * TAX_RATE
totalPrice += item.price + taxAmount
} else {
totalPrice += item.price
}
}
}
}
return totalPrice
}
Problems in above code 👆:
- You can not tell what does this method do, there are too many ifs and else
- There is too much manipulation of the price.
This makes it harder to simplify the code and debug the code in case there is a bug.
To make the above method a bit better we first have to know what id actually does. Give it a read and see what it does.
This method calculates the total price for a list of items, considering availability, price, discounts, and taxes
Lets make some modifications to the above method calculateTotalPrice
Modification : 👇
fun calculateTotalPrice(items: List<Item>): Double {
return items.filter { it.isAvailable && it.price > 0 }
.sumByDouble { calculateItemPrice(it) }
}
private fun calculateItemPrice(item: Item): Double {
val discountedPrice = calculateDiscountedPrice(item)
return if (item.isTaxable) {
val taxAmount = calculateTaxAmount(discountedPrice)
discountedPrice + taxAmount
} else {
discountedPrice
}
}
private fun calculateDiscountedPrice(item: Item): Double {
return if (item.discountPercentage > 0) {
item.price - (item.price * item.discountPercentage / 100)
} else {
item.price
}
}
private fun calculateTaxAmount(price: Double): Double {
return price * TAX_RATE
}
// Item and TAX_RATE are defined constants in the code in this case
In the above 👆 code we extracted the piece of code that can be performed individually. For example
- calculateItemPrice
- calculateDiscountedPrice
- calculateTaxAmount
We extracted the respective logic and put into these methods this made the code readable, modifiable in case we need to. This improved
- Readability
- Modularity
- Maintainability
Each method now has a single responsibility, making the code easier to understand and modify.
2. Duplicate Code :
This is quite self explanatory. If you find the same piece of code at multiple places in the code base that means you can extract that piece to a method or a class and then use it elsewhere.
Bad example : 👇
fun calculateAreaOfSquare(sideLength: Double): Double {
return sideLength * sideLength
}
fun calculateAreaOfRectangle(width: Double, height: Double): Double {
return width * height
}
Things that can be improved
instead of using sideLength*sideLength we can extract it to a method and override it for having methods available for different shapes.
Modification : 👇
fun calculateArea(sideLength: Double): Double {
return sideLength * sideLength
}
fun calculateArea(width: Double, height: Double): Double {
return width * height
}
Doing this helps us achieve code reuse and reduces redundancy.
3. Primitive Obsessions :
This refers to excessive use of primitives in your data class instead of abstracting the attributes and then using those. Let me try to explain using a bad example on which we can do some improvement.
Bad Example: 👇
data class Person(
val name: String,
val age: Int,
val address: String,
val email: String,
...,
....,
...
)
Here we are using all the fields as String. This restrict our datamodel in terms of flexibility. Any time there is some modification we have to go through the whole Data model class and edit it.
A minor modification that can be done is extract few properties from this data model that can act as stand alone attributes. For example Address
Email . After modifications, the new data model class will now look like this.
Modification : 👇
data class Person(
val name: String,
val age: Int,
val address: Address,
val contactInfo: Contact
)
data class Address(
val streetNo: String,
val streetName: String,
val city: String
...)
data class Contact(
val email: String,
val phoneNo: String
)
Adopting this style of attributes inside Person class can enable us to use and reuse Address and contactInfo objects else where in the project. This way we do not have to pass Person object every where. This saves us from exposing the information which is not required by the caller. For example a method that takes just Address information would not require Person properties.
4. Long Parameter List:
We often see a method declared in a codebase which has a really long list of parameters. This is actually easier to implement but harder to keep track of. If you come back to the same code after few days you will take some time to gather and recollect what all parameters are doing and why they are required.
Bad example : 👇
//very trivial use case but couldnt think of any simpler example
fun createUser(val firstName: String, val lastName: String, val sex: String, val age: Int, ......
we can create a data class that has all these fields required by the method createUser and use that as a parameter instead.
Modification: 👇
data class UserInfo(
val firstName: String,
val lastName: String,
val age: Int,
val address: String,
val email: String
)
fun createUser(userInfo: UserInfo): User {
// ...
return user
}
These are few ways we an identify that what a bad code looks like and how we can improve upon it.
Talking about improving the code. You can also learn about SOLID Principles. I have tried to write about that in simple words here.
Happy Coding…
Code Smell Explained… was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.
This content originally appeared on Level Up Coding - Medium and was authored by Anant
Anant | Sciencx (2023-05-01T02:52:58+00:00) Code Smell Explained…. Retrieved from https://www.scien.cx/2023/05/01/code-smell-explained/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.