Допускайте, что ситуация может измениться в худшую сторону
Одним из лучших примеров этой проблемы связан со "знаковым расширением". Большинство компьютеров используют так называемую арифметику "двоичного дополнения". Крайний левый бит отрицательного числа в этой арифметике всегда содержит 1. Например, восьмибитовый тип char со знаком, содержащий число -10, будет представлен в машине с двоичным дополнением как 11110110
(или 0xf6). То же самое число в 16-битовом типе int представлено как 0xfff6. Если вы преобразуете 8-битовый char в int явно посредством оператора приведения типов или неявно, просто используя его в арифметическом выражении, где второй операнд имеет тип int, то компилятор преобразует char в int, добавляя второй байт и дублируя знаковый бит (крайний слева) char в каждом бите добавленного байта. Это и есть знаковое расширение.
Существуют два вида операций сдвига вправо на уровне языка ассемблера: "арифметический сдвиг" дает вам знаковое расширение (то значение, что было в крайнем левом бите до сдвига, будет в нем и после сдвига); "логический сдвиг"
заполняет левый бит нулем. Данное правило, независимо от того, арифметический или логический сдвиг у вас, когда вы используете оператор сдвига Си/Си++, звучит очень просто: если вам требуется знаковое расширение, то допустите, что у вас получится заполнение нулями. А если вам нужно заполнение нулями, то представьте, что у вас получилось знаковое расширение.
Другим хорошим примером являются возвращаемые коды ошибок. Удивительное количество программистов не заботится о проверке того, не вернула ли функция
malloc() значение NULL при недостатке свободной памяти. Быть может, они полагают, что имеется бесконечный объем виртуальной памяти, но известно, что ошибка может с легкостью вызвать резервирование всей имеющейся памяти, и вы никогда не обнаружите этого, если не будете проверять возвращаемые коды ошибок. Если функция может индицировать состояние ошибки, то вы должны допустить, что ошибка произойдет, по меньшей мере,
однажды за время жизни программы.