Continuing the series of blog posts about code smells, let’s talk about Divergent Change
. Divergent change occurs when you have to do too many changes in a class/module to introduce a new feature/change. Imagine the following Product
class:
class Product {
private type: string;
constructor(type: string) {
this.type = type;
}
public getBasePrice() : number {
switch (this.type) {
case 'food':
return 10;
case 'drinks':
return 7;
case 'books':
return 3;
default:
return 0;
}
}
public getTaxPercent() : number {
switch (this.type) {
case 'food':
case 'drinks':
return 24;
case 'books':
return 8;
default:
return 0;
}
}
public getProductCategory(): string {
switch (this.type) {
case 'food':
case 'drinks':
return 'Food and Beverages';
case 'books':
return 'Education';
default:
return '-';
}
}
}
If we want to introduce a new product type, let’s say Vitamins
we would have to make changes in 3 different methods of the class.
class Product {
private type: string;
constructor(type: string) {
this.type = type;
}
public getBasePrice() : number {
switch (this.type) {
case 'food':
return 10;
case 'drinks':
return 7;
case 'books':
return 3;
case 'vitamins':
return 1;
default:
return 0;
}
}
public getTaxPercent() : number {
switch (this.type) {
case 'food':
case 'drinks':
return 24;
case 'books':
return 8;
case 'vitamins':
return 3;
default:
return 0;
}
}
public getProductCategory(): string {
switch (this.type) {
case 'food':
case 'drinks':
return 'Food and Beverages';
case 'books':
return 'Education';
case 'vitamins':
return 'Pharmaceutical';
default:
return '-';
}
}
}
We can improve the code by introducing a mapping:
type ProductInfo = {
tax?: number,
basePrice?: number;
productCategory?: string;
}
class Product {
private type: string;
static productTypes: { [key: string]: ProductInfo } = {
food: {
tax: 24,
basePrice: 10,
productCategory: 'Food and Beverages'
},
drinks: {
tax: 24,
basePrice: 7,
productCategory: 'Food and Beverages'
},
books: {
tax: 8,
basePrice: 3,
productCategory: 'Education'
},
}
constructor(type: string) {
this.type = type;
}
public getBasePrice() : number {
return Product.productTypes[this.type].basePrice || 0;
}
public getTaxPercent() : number {
return Product.productTypes[this.type].tax || 0;
}
public getProductCategory(): string {
return Product.productTypes[this.type].productCategory || '-';
}
}
Now if we want to introduce a new product type (e.g. Vitamins
), we will make changes in a single place without having to touch the methods of the class (with the possibility of introducing a bug affecting the existing products for example).