visit
Most of these smells are just hints of something that might be wrong. Therefore, they are not required to be fixed per se… (You should look into it, though.)
Objects created without arguments are often mutable and erratic
TL;DR: Pass all your essential arguments when creating objects.
public Person();
// Anemic and mutable
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
// We 'pass' the essence to the object
// So it does not mutate
Don't worry about design, if you listen to your code a good design will appear...Listen to the technical people. If they are complaining about the difficulty of making changes, then take such complaints seriously and give them time to fix things.
Martin Fowler
Software Engineering Great Quotes
Exceptions are handy. But should be as narrow as possible
TL;DR: Be as specific as possible when handling errors.
import calendar, datetime
try:
birthYear= input('Birth year:')
birthMonth= input('Birth month:')
birthDay= input('Birth day:')
# we don't expect the above to fail
print(datetime.date(int(birthYear), int(birthMonth), int(birthDay)))
except ValueError as e:
if str(e) == 'month must be in 1..12':
print('Month ' + str(birthMonth) + ' is out of range. The month must be a number in 1...12')
elif str(e) == 'year {0} is out of range'.format(birthYear):
print('Year ' + str(birthYear) + ' is out of range. The year must be a number in ' + str(datetime.MINYEAR) + '...' + str(datetime.MAXYEAR))
elif str(e) == 'day is out of range for month':
print('Day ' + str(birthDay) + ' is out of range. The day must be a number in 1...' + str(calendar.monthrange(birthYear, birthMonth)))
import calendar, datetime
# We might add specialized tries dealing with errors from the following 3 statements
birthYear= input('Birth year:')
birthMonth= input('Birth month:')
birthDay= input('Birth day:')
# try scope should be narrow
try:
print(datetime.date(int(birthYear), int(birthMonth), int(birthDay)))
except ValueError as e:
if str(e) == 'month must be in 1..12':
print('Month ' + str(birthMonth) + ' is out of range. The month must be a number in 1...12')
elif str(e) == 'year {0} is out of range'.format(birthYear):
print('Year ' + str(birthYear) + ' is out of range. The year must be a number in ' + str(datetime.MINYEAR) + '...' + str(datetime.MAXYEAR))
elif str(e) == 'day is out of range for month':
print('Day ' + str(birthDay) + ' is out of range. The day must be a number in 1...' + str(calendar.monthrange(birthYear, birthMonth)))
Code Smell 26 - Exceptions Polluting
Code Smell 73 - Exceptions for Expected Cases
The primary duty of an exception handler is to get the error out of the lap of the programmer and into the surprised face of the user.
Verity Stob
Software Engineering Great Quotes
Hard coding is fine. For a short period of time
TL;DR: Don't leave a hardcoded mess on IFs.
Hard-coding iF conditions is great when doing .
We need to clean up stuff.private string FindCountryName (string internetCode)
{
if (internetCode == "de")
return "Germany";
else if(internetCode == "fr")
return "France";
else if(internetCode == "ar")
return "Argentina";
// lots of elses
else
return "Suffix not Valid";
}
private string[] country_names = {"Germany", "France", "Argentina"} // lots more
private string[] Internet_code_suffixes= {"de", "fr", "ar" } // more
private Dictionary<string, string> Internet_codes = new Dictionary<string, string>();
// There are more efficient ways for collection iteration
// This pseudocode is for illustration
int currentIndex = 0;
foreach (var suffix in Internet_code_suffixes) {
Internet_codes.Add(suffix, Internet_codes[currentIndex]);
currentIndex++;
}
private string FindCountryName(string internetCode) {
return Internet_codes[internetCode];
}
Code Smell 36 - Switch/case/elseif/else/if statements
Don't be (too) clever. My point was to discourage overly clever code because "clever code" is hard to write, easy to get wrong, harder to maintain, and often no faster than simpler alternatives because it can be hard to optimize.
Bjarne Stroustrup
Software Engineering Great Quotes
If it walks like a duck and it quacks like a duck, then it must be a duck
TL;DR: Don't create unnecessary abstractions
Discovering abstractions on the MAPPER is a hard task.
After refining we should remove unneeded abstractions.<?php
Namespace Spelling;
final class Dictionary {
private $words;
function __construct(array $words) {
$this->words = $words;
}
function wordsCount(): int {
return count($this->words);
}
function includesWord(string $subjectToSearch): bool {
return in_array($subjectToSearch, $this->words);
}
}
// This has protocol similar to an abstract datatype dictionary
// And the tests
use PHPUnit\Framework\TestCase;
final class DictionaryTest extends TestCase {
public function test01EmptyDictionaryHasNoWords() {
$dictionary = new Dictionary([]);
$this->assertEquals(0, $dictionary->wordsCount());
}
public function test02SingleDictionaryReturns1AsCount() {
$dictionary = new Dictionary(['happy']);
$this->assertEquals(1, $dictionary->wordsCount());
}
public function test03DictionaryDoesNotIncludeWord() {
$dictionary = new Dictionary(['happy']);
$this->assertFalse($dictionary->includesWord('sadly'));
}
public function test04DictionaryIncludesWord() {
$dictionary = new Dictionary(['happy']);
$this->assertTrue($dictionary->includesWord('happy'));
}
}
<?php
Namespace Spelling;
// final class Dictionary is no longer needed
// The tests use a standard class
// In PHP we use associative arrays
// Java and other languages have HashTables, Dictionaries etc. etc.
use PHPUnit\Framework\TestCase;
final class DictionaryTest extends TestCase {
public function test01EmptyDictionaryHasNoWords() {
$dictionary = [];
$this->assertEquals(0, count($dictionary));
}
public function test02SingleDictionaryReturns1AsCount() {
$dictionary = ['happy'];
$this->assertEquals(1, count($dictionary));
}
public function test03DictionaryDoesNotIncludeWord() {
$dictionary = ['happy'];
$this->assertFalse(in_array('sadly', $dictionary));
}
public function test04DictionaryIncludesWord() {
$dictionary = ['happy'];
$this->assertTrue(in_array('happy', $dictionary));
}
}
Code Smell 111 - Modifying Collections While Traversing
Most of the effort in the software business goes into the maintenance of code that already exists.
Wietse Venema
Software Engineering Great Quotes
Being generic and foreseeing the future is good.
TL;DR: Don't over-generalize
public interface Vehicle {
public void start();
public void stop();
}
public class Car implements Vehicle {
public void start() {
System.out.println("Running...");
}
public void stop() {
System.out.println("Stopping...");
}
}
// No more vehicles??
public class Car {
public void start() {
System.out.println("Running...");
}
public void stop() {
System.out.println("Stopping...");
}
}
// Wait until more vehicles are discovered
On our bijections we need to model existing real-world protocols.
Interfaces are the MAPPER correspondence to protocol.
Dependency injection/Invesion protocols declare interfaces that are fulfilled with their realizations. Until then, they can be empty.If your language defines an interface for test mocking, it is another code smell.
Code Smell 30 - Mocking Business
I love software, because if you can imagine something, you can build it.
Ray Ozzie
Software Engineering Great Quotes