Как найти вонючие части вашего кода [Часть XXVII]
6 декабря 2022 г.Запахи кода — это классика.
Это пахнет, потому что, вероятно, есть много случаев, когда его можно отредактировать или улучшить.
n Большинство этих запахов являются лишь намеками на то, что что-то может быть не так. Следовательно, их не обязательно исправлять как таковые… (Тем не менее, вы должны изучить это.)
Пахнет предыдущим кодом
- Часть I< /а>
- Часть II< /а>
- Часть III< /а>
- Часть IV< /а>
- Часть V< /а>
- Часть VI< /а>
- Часть VII< /а>
- Часть VIII< /а>
- Часть IX< /а>
- Часть X< /а>
- Часть XI< /а>
- Часть XII
- Часть XIII
- Часть XIV
- Часть XV
- Часть XVI
- Часть XVII
- Часть XVIII
- Часть XIX
- Часть XX
- Часть XXI
- Часть XXII
- Часть XXIII
- Часть XXIV
- Часть XXV
- Часть XXVI
Продолжим...
Code Smell 131 — конструктор без аргументов
Объекты, созданные без аргументов, часто изменяемы и ошибочны
<цитата>TL;DR: передайте все необходимые аргументы при создании объектов.
Проблемы
- Изменяемость ли>
- Анемичные модели
Решения
- Используйте один полный и единственный конструктор.
- Избегайте сеттеров и геттеров
Контекст
Обычно используется конструктор без аргументов и множество сеттеров для его изменения.
Beans — хорошо известный пример запаха кода.
Пример кода
Неверно
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
Обнаружение
- [x] Автоматически
Мы можем проверить все конструкторы, но есть несколько ложных срабатываний.
Объекты без сохранения состояния являются допустимым примером.
Теги
- Изменчивость
Заключение
Пустые конструкторы — это намеки на изменчивость и случайные проблемы с реализацией.
Нам необходимо изучить способы использования, чтобы улучшить наши решения.
Отношения
Code Smell 01 — Анемичные модели
Подробнее
- Нулевой конструктор
- Обнаженные модели — Часть I: Сеттеры
- Обнаженные модели — Часть II: Добытчики
- Злая сила мутантов а>
Кредиты
Фото Аде Адебовале на Unsplash
<цитата>
Не беспокойтесь о дизайне, если вы прислушаетесь к своему коду, появится хороший дизайн... Прислушивайтесь к техническим специалистам. Если они жалуются на трудности внесения изменений, отнеситесь к таким жалобам серьезно и дайте им время исправить ситуацию.
Мартин Фаулер
Великие цитаты о разработке программного обеспечения
Code Smell 132 – Исключение: попытка слишком широкая
Исключения удобны. Но должен быть как можно более узким
<цитата>TL;DR: Будьте максимально конкретны при обработке ошибок.
Проблемы
- Нарушение принципа Fail fast
- Отсутствующие ошибки
- Ложноотрицательные результаты
Решения
- Максимально сузьте обработчик исключений
Пример кода
Неверно
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)))
Обнаружение
- [x] Руководство
Если у нас есть достаточно хороший набор тестов, мы можем выполнить мутационное тестирование, чтобы максимально сузить область исключений.
Теги
- Исключения
Заключение
Мы должны делать исключения как можно хирургичнее.
Отношения
Code Smell 26 — Исключения, загрязняющие окружающую среду
Code Smell 73 — Исключения для ожидаемых случаев
Кредиты
Фотон от Якоба Брауна на Unsplash
<цитата>
Основная обязанность обработчика исключений — передать ошибку из-под опеки программиста и передать удивленному лицу пользователя.
Верити Стоб
Великие цитаты о разработке программного обеспечения
Code Smell 133 — Жестко запрограммированные условия IF
Жесткое кодирование — это нормально. Ненадолго
<цитата>TL;DR: не оставляйте жестко запрограммированный беспорядок в IF.
Проблемы
- Тестируемость
- Жестко закодированные значения
- Нарушение принципа открытости/закрытости
Решения
- Замените все IF динамическим условием или полиморфизм.
Контекст
Жесткое кодирование условий iF отлично подходит для разработки через тестирование.
Нам нужно убрать вещи.
Пример кода
Неверно
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];
}
Обнаружение
- [x] Автоматически
Проверив условия If/else, мы можем обнаружить жестко закодированные условия.
Теги
- IF
Заключение
Раньше жесткое кодирование было невозможно.
С помощью современных методологий мы учимся путем жесткого кодирования, а затем обобщаем и рефакторим наши решения.
Отношения
Code Smell 36 — Switch/case/elseif Операторы /else/if
Code Smell 102 – код стрелки< /p>
Подробнее
Кредиты
Фото Джессики Джонстон на Unsplash
<цитата>
Не будь (слишком) умным. Моя цель состояла в том, чтобы не поощрять слишком умный код, потому что «умный код» трудно написать, легко ошибиться, сложнее поддерживать и часто не быстрее, чем более простые альтернативы, потому что его трудно оптимизировать.
Бьерн Страуструп
Великие цитаты о разработке программного обеспечения
Code Smell 134 – Специализированные бизнес-коллекции
Если он ходит как утка и крякает как утка, значит, это утка
<цитата>TL;DR: не создавайте ненужных абстракций
Проблемы
- Над дизайном
- Ненужные классы
Решения
- Используйте стандартный класс
Контекст
Обнаружение абстракций в MAPPER – сложная задача.
После уточнения мы должны удалить ненужные абстракции.
Пример кода
Неверно
<?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 PHPUnitFrameworkTestCase;
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 PHPUnitFrameworkTestCase;
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));
}
}
Обнаружение
- [x] Полуавтоматический
Исходя из протоколов, мы должны удалить некоторые ненужные классы
Исключения
Иногда нам нужно оптимизировать коллекции из соображений производительности, если у нас есть достаточно убедительных доказательств.
Теги
- Протоколы
Заключение
Время от времени нам нужно очищать код.
Хорошей отправной точкой являются специализированные коллекции.
Отношения
Code Smell 111 – изменение коллекций при обходе
Подробнее
Кредиты
Фото Писита Хенга на Unsplash
<цитата>
Большая часть усилий в сфере программного обеспечения уходит на поддержку уже существующего кода.
Витсе Венема
Великие цитаты о разработке программного обеспечения
Code Smell 135 — Интерфейсы всего с одной реализацией
Хорошо быть универсальным и предвидеть будущее.
<цитата>TL;DR: не обобщайте слишком много
Проблемы
- Предполагаемый дизайн
- Сложность
- Излишняя инженерия
Решения
- Удаляйте интерфейс, пока не получите больше примеров.
Контекст
В прошлом программисты просили нас проектировать с учетом изменений.
Сегодня мы следуем научному методу.
Всякий раз, когда мы находим дублирование, мы удаляем его.
Не раньше.
Пример кода
Неверно
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
Обнаружение
- [x] Автоматически
Это очень просто для наших линтеров, поскольку они могут отследить эту ошибку во время компиляции.
Исключения
Это правило применяется к межсистемному определению и бизнес-логике.
Некоторые платформы определяют интерфейс как протокол, который необходимо выполнить.
На наших биекциях нам нужно смоделировать существующие протоколы реального мира.
Интерфейсы — это MAPPER соответствие протоколу.
Протоколы внедрения зависимостей/внедрения объявляют интерфейсы, которые выполняются с их реализациями. До тех пор они могут быть пустыми.
Если ваш язык определяет интерфейс для имитации тестов, это еще один запах кода.
Теги
- Над дизайном
Отношения
Code Smell 130 — AddressImpl р>
Code Smell 30 — Насмешливый бизнес
Code Smell 136 — классы только с одним подклассом
Заключение
Нужно ждать абстракций, а не заниматься творчеством и спекуляциями
Кредиты
Фото Brian Kostiuk на Unsplash р>
<цитата>
Я люблю программное обеспечение, потому что если вы можете что-то представить, вы можете это создать.
Рэй Оззи
Великие цитаты о разработке программного обеспечения
И это пока все…
В следующей статье мы расскажем еще о пяти запахах кода!
Оригинал