Рубрики
Без рубрики

Слушайте Компилятор!

Многие программисты, особенно начинающие (или еще учащиеся), склонны игнорировать предупреждения компилятора 🙂 Я бы… Помеченный java, cpp, ошибками, типами.

Многие программисты, особенно начинающие (или еще учащиеся), склонны игнорировать предупреждения компилятора:)

Я хотел бы поделиться двумя примерами – первый – это “реальная жизнь” с некоторых моих прошлых работ. Другой – недавний, заданный на форуме на моем хобби-сайте.

#1 Потеря ежедневных отчетов о доходах:)

Несколько лет назад я работал над проектом, в котором использовались POS-терминалы для сбора платежей за проезд в автобусе с бесконтактных карт. Один из моих коллег адаптировал новую версию терминала к проекту и, как мне кажется, сделал это успешно. Затем он перешел на другой проект (банковское дело).

Внезапно, когда этот новый терминал был опробован в реальных условиях, была обнаружена ужасная проблема. Он отправлял отчет о деньгах в конце дня (через встроенный сотовый телефон, например, модем) – но часто этот отчет никогда не просматривался на принимающем сервере! И терминал, отправив его, удалял отчет из внутреннего хранилища.

Наш клиент терял отчеты о деньгах! В худшем случае некоторые сотрудники клиента могут оказаться в тюрьме за мошенничество несмотря на то, что они действительно невиновны!

Я потратил около дня, чтобы выяснить, в чем дело. Это было до смешного просто:

// in terminal API all modem functions
// return negative value on error, like this

/** returns number of bytes sent or negative error code */
int MODEM_SendData(unsigned char* buffer, int count);

// in the code my colleague did the following

unsigned int result = MODEM_SendData(fileData, fileLength);
if (result < 0) {
    reportError("Sending report failed");
}

Таким образом, он сохранял результат операции в переменной, которая могла иметь только неотрицательные значения – и, конечно же, отрицательные значения ошибок были потеряны.

Я обнаружил он намеренно отключил предупреждения компилятора, крича, что результат < 0 никогда не будет истинным – когда я спросил его, он сказал: “Ха-ха, я не понял, о чем речь, подумал, что это какое-то глупое бесполезное сообщение, нарушающее компиляцию”. (мы использовали политику “рассматривать предупреждение как ошибку”)

Итак, речь шла о неосторожном обращении с типами, к сожалению, это легко в C++ и таких языках, как Java или Go, которые маниакально пытаются предотвратить такие ошибки.

#2 Дженерики Java и необработанные типы

Второй пример на Java, и он взят из этой темы форума (найдите где-нибудь ниже сообщение “Радован Маркус”). Коллега жалуется, что:

Я получил такую ошибку, когда нажал кнопку compile с помощью “java”. “Примечание: примерные значения.java использует непроверенные или небезопасные операции. Примечание: Перекомпилируйте с помощью -Xlint: снимите флажок для получения подробной информации.”

Код содержит такие вещи, как это:

import java.util.ArrayList;
import javafx.util.Pair;

// ...

ArrayList> primes = new ArrayList<>();

// ...

inputNumbers.add(new Pair(2, 1));

Здесь ситуация более тонкая. Хотя детали (и номера строк) можно было бы увидеть, если компилятор выполняется с флагом -Xlint:unchecked , как следует из сообщения.

Дело в том, что новая пара(2, 1) создает Пара объект “необработанного типа”, с удаленными типами компонентов. Это разрешено для совместимости со старым кодом, когда в java не было параметров типа (до версии 1.5). Чтобы исправить это, мы должны указать, что хотим использовать пары с определенными типами:

new Pair(2, 1);

// or, since java 1.7, indicate it is typed, but implicitly:
new Pair<>(2,1);

В некоторых из более поздних строк такое исправление приведет к ошибке:

long number;
int index;
// ...
inputNumbers.add(new Pair<>(number, index));

это потому, что набрано Пара<Целое число, Целое число> не хочу удерживать long , если она явно не преобразована:

inputNumbers.add(new Pair<>((int)number, index));

Это может показаться капризным потому что код все равно работает. Но это работает только по счастливой случайности (так как используемые операции не будут прерываться при поиске Длинный вместо Целое число внутри пары).

Однако со стертыми типами мы могли бы поместить внутрь совершенно другой тип, например Строка :

inputNumbers.add(new Pair("2", 1));

Это будет хорошо скомпилировано (просто с предупреждением, как указано выше), но сломается во время выполнения, если значение из этой пары используется в каком-либо вычислении (но не сломается, если оно используется только для печати).

В корпоративных проектах такие ошибки могут оставаться незамеченными в течение длительного времени , если код с недостатком не покрывается тестами или условие ошибки обычно не выполняется.

Вот почему строго типизированные языки иногда предпочтительнее для корпоративных проектов – ради повышения надежности – компилятор может обнаружить и предотвратить некоторые виды ошибок, которые неизбежны, когда многие люди работают над одним и тем же большим кодом месяцами и годами.

Извините за такое количество писем и спасибо, что прочитали так далеко:)

Оригинал: “https://dev.to/rodiongork/listen-to-compiler-3529”