- Используйте в чистом видео только числа
0
и1
, а любые другие числа храните как переменные с понятными именами или как именованные константы - Если в программе есть возможность деления на ноль, добавьте проверку, чтобы предупредить такую ошибку
- Не сравнивайте данные разных типов — выполните преобразование типов вручную перед сравнением
- Обратите внимание на предупреждения компилятора и исправьте связанные с ними проблемы
Основная проблема, связанная с использованием целых чисел, — проблема переполнения — возникает, когда число, которое нужно сохранить, превышает возможности выбранного типа. Например, если попытаться сохранить 1 000 000 000 000
в переменную типа int
. Поэтому, при выполнении арифметических операций, нужно принимать во внимание наибольшие возможные значения целых чисел — как для конечного результата вычисления, так и для промежуточных этапов. И, исходя из этого, выбирать подходящий тип числа.
Интервалы значений некоторых целых типов
Число | Без знака | Со знаком |
---|---|---|
8-битное | от 0 до 255 |
от -128 до 127 |
16-битное | от 0 до 65 535 |
от -32 768 до 32 767 |
32-битное | от 0 до 4 294 967 295 |
от -2 147 483 648 до 2 147 484 647 |
64-битное | от 0 до 18 446 744 073 709 551 615 |
от -9 223 372 036 854 775 808 до 9 223 372 036 854 775 807 |
Предположим, что нам нужно выполнить такое вычисление на Java.
class Main {
public static void main(String[] args) {
int product = (1000000 * 1000000) / 1000000
System.out.println(product); // Ожидаем 1000000, а получим -727
}
}
Несмотря на то, что в результате деления мы ожидаем получить 1 000 000
, который может быть представлен как 32-битное число, промежуточная операция умножения вернёт 1 000 000 000 000
, который программа не сможет корректно запомнить и вместо этого запомнит -727 379 968
(это значение можно рассчитать по формуле). Именно поэтому после деления мы получим -727
, а вовсе не 1 000 000
.
Чтобы решить проблему переполнения, мы можем использовать тип long
или класс BigInteger
, который реализует числа произвольной длины (arbitary-precision integers), то есть такие числа, в которых количество цифр и количество знаков после запятой ограничено только доступной памятью.
import java.math.*;
class Main {
public static void main(String[] args) {
BigInteger a = BigInteger.valueOf(1000000);
BigInteger b = BigInteger.valueOf(1000000);
BigInteger product = a.multiply(b).divide(BigInteger.valueOf(1000000));
System.out.println(product); // 1000000
}
}
В некоторых языках, например, в Python по умолчанию используются числа произвольной длины.
Главная особенность применения чисел с плавающей запятой в том, что многие дробные десятичные числа не могут быть точно представлены с помощью нулей и единиц, используемых в цифровом компьютере. В бесконечных десятичных дробях, таких как 1/3 или 1/7, обычно сохраняются только 7 или 15 цифр после запятой (с. 286).
- Избегайте сложения и вычитания слишком разных по размеру чисел (например, 32-битного числа недостаточно, чтобы сохранить разницу между
1 000 000
и0.1
, из-за чего возникнет ошибка) - Избегайте сравнения чисел с плавающей точкой на равенство, потому что их действительное значение может отклоняться от ожидаемого через несколько знаков после запятой (например, ожидаем
1.0
, а имеем1.000000000001
) - Предупреждайте ошибки округления
- Используйте большую точность, чем это необходимо
- Используйте binary-coded decimal (BCD)
- Используйте целые числа, например, учитывайте деньги в центах вместо долларов
- Используйте для хранения строк переменные с понятным именем
- Если эта строка встречается чаще одного раза
- Если по тексту строки непонятно, что она означает (например,
0x1B
)
- Используйте для хранения строк внешние файлы
- Если планируется перевод строк на несколько языков (можно использовать
po
иmo
файлы в рамках системы интернационализацииgettext
) - Если строк очень много и есть риск, что возникнет проблема с их хранением из-за недостатка памяти (оптимизировать хранение отдельных файлов легче, чем оптимизировать всё приложение)
- Если планируется перевод строк на несколько языков (можно использовать
- Используйте
Unicode
, если планируете интернационализацию приложение. Если нет, можно рассмотреть другие кодировки, например,ISO 8859
- Разработайте стратегию интернационализации на самом раннем периоде развития программы
Основные принципы:
- Вместо простой проверки логического выражения, присвойте его значение переменной с понятным именем
# Плохо :(
if (product.is_available && product.count >= order.count) {
order.process()
}
# Лучше!
order_is_ready_to_process = product.is_available && product.count >= order.count
if (order_is_ready_to_process) {
order.process()
}
- Раскладывайте сложные, многосоставные условия на несколько переменных с понятными именами
# Плохо :(
if (order.status == ORDER_STATUSES.NEW && (product.is_available && product.count >= order.count || product.has_infinite_count)) {
order.process()
}
# Лучше!
order_is_new = order.status == ORDER_STATUSES.NEW
products_are_enough = (product.count >= order.count) || product.has_infinite_count
if (order_is_new && products_are_enough) {
order.process()
}
Давать логическим переменным понятные имена — отличный инструмент в борьбе со сложностью.
Перечислимый тип данных (англ. enumeration, enumerated type) — тип данных, чьё множество значений представляет собой ограниченный список идентификаторов.
Перечислимые типы данных:
- Улучшают читабельность в сравнении с числовыми константами
- Повышают надёжность, потому что компилятор во многих языках может провести более тщательную проверку, чем при работе с целыми значениями и константами
- Упрощают модифицируемость программы за счёт возможности легко добавить новое значение к переменной
- Хорошая альтернатива логическим переменным, когда состояние становится слишком сложным для передачи с помощью булевых значений
Именованные константы аналогичны переменным за исключением того, что вы не можете изменить значение константы после её инициализации (с. 299)
Применение именованных констант — это отличный способ хранения некоторых характеристик программы, которые потенциально могут измениться.
В некоторых языках именованные константы не поддерживаются, но можно иметировать их при помощи обычных переменных. Например, модуль settings.py
в фреймворке Django (на языке Python) обычно состоит из переменных, которые выполняют роль именованных костант — помогают параметризировать программу.
Общий принцип заключается в том, что необходимо:
- стремиться к замене всех литерал на именованные константы,
- последовательно их использовать (заменять определённый литерал на константу во всём коде, а не в нескольких отдельных модулях).
Это позволит сэкономить время и силы на сопровождении программы.
Даже если вы считаете, что литеральное значение безопасно, используйте вместо него именованную константу. Фанатично искорените литералы из вашего кода (с. 301)
Массив состоит из группы элементов одинакового типа, доступ к которым осуществляется напрямую по индексу (с. 301)
- Рассмотрите возможность применения типов
Set
,Stack
,Queue
как альтернативу, прежде чем выбрать массив - Убедитесь, что используемые значения индексов не выходят за пределы массива и что обращения к крайним элементам осуществляются по правильным индексам
- Убедитесь, что при обходе многомернывх массивов индексы используются в правильном порядке
Типы данных, определяемые программистом, — одна из наиболее мощных возможностей, позволяющих наиболее чётко обозначить ваше понимание программы.
Применение пользовательских типов данных позволяет изолировать потенциальные изменения на уровне этого типа и в дальшнейшем быстрее справляться с изменениями в формате данных. Например, легче изменить пользовательский тип данных Coordinate
, чем поменять по всему коду в нужных местах float
на double
😓.
Если есть даже малейшая вероятность, что тип может измениться, лучше использовать пользовательский тип вместо обычного.
Основные принципы:
- Имя типа должно отражать элементы предметной обалсти, которые этот тип представляет, а не сами данные и их формат
- Не следует переопределять типы, уже определённые в вашем языке
- Стоит всегда рассматривать создание класса вместо использования
typedef
, так как во многих случаях нужно больше гибкости и управляемости, чем может предоставить пользовательский тип ⚖️
Пользовательски типы поддерживается лишь в некоторых языках, например, в С, С++ или Pascal.