Ошибки программирования

Содержание

Ошибки, которые обнаруживает компилятор, называют синтаксическими ошибками или ошибками компиляции. Синтаксические ошибки являются результатом ошибок в конструкции кода, таких как неправильное написание ключевого слова, пропуск необходимого знака пунктуации или использование открывающей фигурной скобки без соответствующей закрывающей фигурной скобки. Эти ошибки обычно легко обнаружить, поскольку компилятор говорит вам, где они находятся и что стало их причиной. Пример программы с синтаксической ошибкой:

Попытка компиляции приведённого кода:

Будет сообщено о четырёх ошибках, но в действительности программа содержит две ошибки:

  • Во второй строке отсутствует ключевое слово void перед main
  • Строка Welcome to Java должна быть закрыта закрывающей кавычкой в третьей строчке программы

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

Совет: если вы не знаете, как исправить ошибку, внимательно сравните вашу программу, символ за символом с похожими примерами в тексте. На начальном этапе обучения вы, вероятно, будете проводить много времени исправляя ошибки синтаксиса. Скоро вы будете знакомы с синтаксисом Java и сможете быстро исправлять синтаксические ошибки.

2. Ошибки во время выполнения

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

Другой пример ошибок во время выполнения – это деление на ноль. Это происходит, когда в целочисленном деление делитель равен нулю. Пример программы, которая вызовет ошибку во время выполнения:

3. Логические ошибки

Логические ошибки происходят, когда программа неправильно выполняет то, для чего она была создана. Ошибки этого рода возникают по многим различным причинам. Допустим, вы написали программу, которая конвертирует 35 градусов Цельсия в градусы Фаренгейта следующим образом:

Вы получите 67 градусов по Фаренгейту, что является неверным. Должно быть 95.0. В Java целочисленное деление показывает только часть – дробная часть отсекается, по этой причине в Java 9 / 5 это 1. Для получения правильного результата, нужно использовать 9.0 / 5, что даст результат 1.8.

Обычно ошибки синтаксиса легко обнаружить и легко исправить, поскольку компилятор даёт указания откуда пришла ошибка и что не так. Ошибки во время выполнения не трудны для поиска, поскольку причина и место для этих ошибок также показывается в консоли во время прерывания программы. Поиск логических ошибок, в свою очередь, очень сложный. В последующих главах вы обучитесь техникам трассировки программ и поиска логических ошибок.

4. Распространённые ошибки

Пропуск закрывающей фигурной скобки, пропуск точки с запятой, пропуск кавычки для строки и неправильное написание имён – всё это самые распространённые ошибки для новых программистов.

Частые ошибки 1: Пропущенные фигурные скобки

Фигурные скобки используются для обозначения в программе блоков. Каждой открывающей фигурной скобке должна соответствовать закрывающая фигурная скобка. Распространённая ошибка – это пропуск закрывающей фигурной скобки. Чтобы избежать эту ошибки, печатайте закрывающую фигурную скобку всякий раз, когда печатаете открывающую фигурную скобку как показано в следующем примере:

Если вы используете IDE такую как NetBeans и Eclipse, то IDE автоматически вставит закрывающую фигурную скобку каждой введённой вами открывающей фигурной скобки.

Частые ошибки 2: Пропуск точки с запятой

Каждая инструкция заканчивается ограничителем инструкции (;). Часто новые программисты забывают поместить ограничитель инструкции для последней инструкции в блоке как это показано в следующем примере:

Частые ошибки 3: Пропуск кавычки

Строки должны помещаться в кавычки. Часто начинающие программисты забывают поместить кавычку в конце строки как показано в следующем примере:

Если вы используете IDE, такую как NetBeans и Eclipse, то IDE автоматически вставит закрывающую кавычку каждый раз, когда вы ввели открывающую кавычку.

Частые ошибки 4: Неправильное написание имён

Java чувствительная к регистру. Неправильное написание имён – частая ошибка для новых программистов. Например, пишут слово main как Main, а вместо String пишут string. Пример:

Первая ошибка начинающего программиста заключается в отсутствии плана

Как и весь письменный контент, будь то курсовая или статья, код не может быть легко создан. Каждый программист должен знать: чтобы написать приложение или отдельный модуль, необходимо придерживаться следующего порядка действий:

обдумать, исследовать, составить план, написать код, протестировать его, изменить то, что требует изменений.

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

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

Начинающий программист недооценивает кодстайл

При написании кода важно учитывать его читабельность. Новички пренебрегают пробелами, полноценными и понятными именами переменных, предпочитая не обращать на эти вещи внимания и называть первую переменную “a”, вторую – “b” и т. д. Никогда не недооценивайте важность чистоты кода. В интернете есть множество статей, посвящённых кодстайлу для каждого из ЯП. Наши материалы по общим рекомендациям:

Всегда думайте, будто парень, который будет поддерживать ваш код, – это жестокий психопат, который знает, где вы живёте.” – Джон Вудс.

Выбирает первое попавшееся решение

Каждый программист, сталкивающийся с проблемой, пытается её решить, что естественно. Опытный программист знает, что нельзя применять к своему коду первое попавшееся решение. Необходимо сравнить его с остальными найденными и выбрать оптимальное. Новичок, скорее всего, использует первое попавшееся решение и даже не заметит, что, допустим, сложность алгоритма возрастёт, а соответственно возрастёт и время его выполнения.

Работа в качестве профессионального программиста заключается не в том, чтобы просто найти решение, а в том, чтобы выбрать самое простое и оптимальное. Код должен быть достаточно простым и для дальнейшей поддержки.

Как сказал Энтони Ричард Хоар, существует два принципа написания ПО:

  1. Первый: написать код настолько просто, что в нём не будет недостатков.
  2. Второй: сделать код настолько сложным, чтобы в нём нельзя было найти недостатки.

Не может бросить код незаконченным

Ещё одна из самых распространённых ошибок заключается в том, что после выбора неоптимального решения новички не хотят расставаться с написанным кодом. Вероятно, это психологически связано с настроем “не бросать недоделанным”. Такое убеждение играет хорошую роль в большинстве видов деятельности, но не в отдельных случаях в программировании.

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

Пятая ошибка начинающего программиста в том, что он не гуглит

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

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

Однако если вы новичок, читающий эту статью, то будьте осторожны с этим пунктом. Большинство из начинающих грешит копированием чужого кода без понимания. Даже если этот код решает вашу проблему, вы никогда не должны использовать его без полного понимания.

Шестая ошибка начинающего программиста – он использует не подходящие структуры данных

При подготовке к собеседованию начинающие программисты уделяют достаточно времени алгоритмам. Если вы знаете множество структур данных и умеете их применять, для работодателя это явно будет плюсом. Однако этого бывает недостаточно. Важным моментом является “уместность” применения этих структур.

Вот два небольших примера:

Использование списков (массивов) вместо хешей (объектов) для управления записями.

Да, для управления списком записей рекомендуется использовать хеш. Хоть это правило больше подходит для случая с большим количеством данных, лучше использовать его постоянно. Почему так? Потому что при поиске записей с помощью идентификаторов, хеши гораздо быстрее списков.

Неприменение стеков.

При написании кода, требующего рекурсии, всегда возникает соблазн использовать простые рекурсивные функции. Как правило, рекурсивный код трудно оптимизировать, особенно в однопоточной среде. Новички зачастую игнорируют альтернативу рекурсивных функций – стековую структуру. Можно добавлять вызовы функций в стек и доставать их, когда нужно пройтись по вызовам в обратном порядке.

“Недорефакторинг”

Обычно под словом “рефакторинг” подразумевается улучшение кода для более ясного понимания и оптимизации. Однако не все новички умеют улучшать читабельность своего кода.

Например, сделать проект более грязным может дублирующийся код. Вместо написания функции с необходимыми параметрами, новички обычно просто копипастят раздел кода, чтобы изменить одну-единственную строчку; объясняют они это “введением нового функционала”. Если вы выбираете стул, то согласитесь, что рациональнее купить один регулируемый по высоте, вместо десяти разной высоты.

Пишет комментарии об очевидных вещах

Есть один способ избежать комментариев и сделать код более понятным – заменять комментарии на очевидно заданные элементы.

Вместо следующего кода:

можно написать такой:

Одно только использование понятных имён для функций делает большинство комментариев ненужными. Комментарии нужны тогда, когда нужно объяснить, почему данный код находится здесь, вместо того чтобы объяснять, что именно делает этот код.

Если вам нужно написать пояснения к коду, не пишите об очевидных вещах. Ниже пример кода, бесполезные комментарии в котором создают лишний шум:

Девятая ошибка начинающего программиста – отсутствие тестов

Одна из самых распространённых ошибок. Скорее всего, если вы не пишете тесты, то проверяете код вручную. В случае создания веб-приложения, вы, наверняка, обновляете приложение после нескольких написанных строк кода. В таком тестировании нет ничего плохого. Однако сложный код всё-таки стоит проверять автоматическим способом. Обычно пишется скрипт, который выполняет определённые действия после добавления n-ого количества строк кода в проект. Чтобы не забывать тестировать приложение после каждого внесённого изменения, используйте компьютер.

Одержимость производительностью

«Преждевременная оптимизация – корень всех зол (как минимум, большей их части) в программировании– Дональд Кнут, 1974.

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

Безусловно, существуют способы оптимизации, которые вы должны применять практически во всех случаях. Например, в Node. js крайне важно, чтобы вы не переполнили цикл событий или не блокировали стек вызовов.

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

Начинающие разработчики не ставят себя на место пользователя

Опытный разработчик представляет себе то, что нужно пользователям. Он думает о том, как облегчить пользователю работу с приложением и упростить доступ к тем или иным функциям.

То, чем заставляют заниматься в школах/ВУЗах, – изобретение колеса

Этот момент довольно спорный, потому что изобретать колесо иногда полезно. Такой способ помогает лучше понять алгоритм/структуру/функцию и, в случае чего, реализовать собственный алгоритм/структуру/функцию, но, например, с расширенным функционалом.

С другой стороны, если вам нужно “обычное колесо”, которое не требует никакого дополнительного функционала/оптимизации, не изобретайте его повторно. Просто используйте то, что написано уже до вас. Не тратьте своё время на изобретение лучшего “обычного колеса”. Постарайтесь пользоваться “колёсами” с открытым исходным кодом, чтобы их можно было легко отлаживать, добавлять функционал и заменять при необходимости.

Отторжение code review

Один из признаков начинающего программиста – восприятие code review как осуждение, критику. Он недоволен этой критикой, не ценит её или даже боится. Это в корне неправильное отношение к ревью. Если вы так себя чувствуйте, убедительно рекомендуем пересмотреть и изменить своё отношение. Смотрите на каждое ревью как на обучение, возможность узнать что-то новое. Самое главное, благодарите своих рецензентов, когда они вас чему-то учат. Большинство из них поделится с вами опытом, который, возможно, упростит вашу деятельность и положительно скажется на продуктивности.

Как уменьшить вероятность ошибки на этапе написания кода. Заметка N4

Это уже четвертая заметка, где я хочу поделиться полезными наблюдениями о паттернах ошибок и том, как можно с ними бороться. В этот раз я затрону такую тему, как обработка редких и аварийных ситуаций в программах. Рассматривая множество программ, я пришел к выводу, что код обработки ошибок в Си/Си++ программах — одно из самых ненадежных мест.

К чему приводят такие дефекты? Программа вместо того, чтобы выдать сообщение «файл X не найден», падает и заставляет пользователя гадать, что он не так делает. Программа для работы с базой данных выводит невразумительное сообщение вместо того, чтобы сообщить, что неверно заполнено одно из полей. Попробуем сразиться с этой разновидностью ошибок, которые досаждают нашим пользователям.

Введение

Вначале информация для читателей, которые не знакомы с моими предыдущими заметками. Их можно найти здесь:

Как всегда, я буду не абстрактен, а начну с примеров. В этот раз примеры будут взяты из исходного кода Firefox. Я постараюсь продемонстрировать, что даже в качественном и известном приложении с кодом для обработки ошибок, всё обстоит не самым лучшим образом. Дефекты были найдены мной с помощью анализатора PVS-Studio 4.50.

a0078_Firefox_ru/image1.png

Примеры ошибок

Пример N1. Неполноценная проверка целостности таблицы

Диагностика PVS-Studio: V579 The strncmp function receives the pointer and its size as arguments. It is possibly a mistake. Inspect the third argument. affixmgr. cpp 3708

Здесь сделана попытка проверить целостность таблицы. К сожалению, эта проверка может сработать, а может и не сработать. Для вычисления длины ключевого слова используют оператор sizeof(), что естественно некорректно. В результате, работоспособность кода зависит от счастливого стечения обстоятельств (длины ключевого слова, размера указателя ‘keyword’ в текущей модели данных).

Пример 2. Неработающая проверка при чтении файла

Диагностика PVS-Studio: V547 Expression ‘c < 0’ is always false. Unsigned type value is never < 0. updater. cpp 1179

Это пример, когда код обработки ошибки пишется по принципу «чтобы было». Программист даже не задумался, что собственно он написал, и как это будет работать. Проверка некорректна. Функция fread() возвращает количество прочитанных байт с помощью беззнакового типа. Прототип функции:

Естественно, для хранения результата используется переменная ‘c’, имеющая тип size_t. Как следствие, результат проверки (c < 0) всегда ложен.

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

Аналогичную ошибку можно увидеть и в других местах:

V547 Expression ‘c < 0’ is always false. Unsigned type value is never < 0. updater. cpp 2373

V547 Expression ‘c < 0’ is always false. Unsigned type value is never < 0. bspatch. cpp 107

Пример 3. Проверка указателя на NULL уже после его использования

Диагностика PVS-Studio: V595 The ‘mShell’ pointer was utilized before it was verified against nullptr. Check lines: 1107, 1109. nsselection. cpp 1107

Если указатель равен нулю, то мы должны обработать этот специальный случай и вернуть из функции NS_OK. Смущает то, что указатель mShell до этого момента уже используется.

Скорее всего, этот код работает, так как указатель mShell всегда неравен NULL. Пример я привожу, чтобы показать, что можно допустить ошибку даже в очень простых проверках. Проверка есть, а смысла от неё нет.

Пример 4. Проверка указателя на NULL уже после его использования

Диагностика PVS-Studio:V595 The ‘* jitp’ pointer was utilized before it was verified against nullptr. Check lines: 547, 549. compiler. cpp 547

Кстати, использование указателя до проверки — распространенная ошибка. Это ещё один пример на эту тему.

Пример 5. Неполная проверка входных значений

Диагностика PVS-Studio: V501 There are identical sub-expressions ‘unit [0] == eCSSUnit_Null’ to the left and to the right of the ‘||’ operator. nsstyleanimation. cpp 1767

Кажется, здесь сразу 2 опечатки. Затрудняюсь сказать, как именно должен выглядеть здесь код, но, наверное, хотели написать так:

Из-за опечаток функция может начать обрабатывать некорректные входные значения.

Пример 6. Неполная проверка входных значений

Диагностика PVS-Studio: V501 There are identical sub-expressions to the left and to the right of the ‘&&’ operator: aXResolution > 0.0 && aXResolution > 0.0 nspresshell. cpp 5114

А вот ещё один пример неудачной проверки входных параметров. В этот раз из-за опечатки не проверяется значение аргумента aYResolution.

Пример 7. Неразыменованный указатель

Диагностика PVS-Studio: V528 It is odd that pointer to ‘char’ type is compared with the ‘\0’ value. Probably meant: *token == ‘\0’. svgnumberlist. cpp 96

Проверка, что между запятыми ничего нет, не работает. Чтобы узнать, пустая строка или нет, можно сравнить первый символ с ‘\0’. Но здесь с нулем сравнивается не первый символ, а указатель. Этот указатель всегда неравен нулю. Корректная проверка должна была выглядеть так: (*token == ‘\0’).

Пример 8. Неподходящий тип для хранения индекса

Диагностика PVS-Studio: V547 Expression ‘index < 0’ is always false. Unsigned type value is never < 0. nsieprofilemigrator. cpp 622

Функция не вернёт PR_FALSE, если в строке нет точки и продолжит работать с некорректными данными. Ошибка в том, что для переменной ‘index’ выбран беззнаковый тип данных. Проверка (index < 0) не имеет смысла.

Пример 9. Формирование неправильного сообщения об ошибке

Диагностика PVS-Studio: V576 Incorrect format. Consider checking the third actual argument of the ‘fwprintf’ function. The pointer to string of wchar_t type symbols is expected. cairo-win32-surface. c 129

Даже если ошибка успешно обнаружена, её еще надо суметь правильно обработать. А поскольку обработчики ошибок тоже никто не тестирует, то там можно увидеть много интересного.

Функция _cairo_win32_print_gdi_error() распечатает абракадабру. В качестве третьего аргумента функция fwprintf() ожидает указатель на unicode-строку, а вместо этого получает строку в формате ‘const char *’.

Пример 10. Ошибка записи дампа

Диагностика PVS-Studio: V547 Expression is always true. Unsigned type value is always >= 0. exception_handler. cc 846

Это другой пример в обработчике ошибок. Здесь некорректно обрабатывается результат, возвращаемый функцией SuspendThread. Переменная last_suspend_cnt имеет тип DWORD, а значит она всегда будет больше или равна 0.

О других ошибках в Firefox

Сделаю небольшое отступление и расскажу о результатах проверки Firefox в целом. Проект качественен, и PVS-Studio выявил мало ошибок. Однако, так как он большой, то в количественном отношении ошибок достаточно много. К сожалению, мне не удалось полноценно изучить отчет, выданный инструментом PVS-Studio. Дело в том, что для Firefox отсутствует файл проекта для Visual Studio. Проект проверялся консольной версией PVS-Studio, вызываемой из make-файла. Открыв отчет в Visual Studio, можно просмотреть все диагностические сообщения. Но раз нет проекта, то Visual Studio не подсказывает, где какие переменные объявлены, не позволяет перейти в место определения макросов и так далее. В результате, анализ неизвестного проекта крайне трудоемок, и я смог изучить только часть сообщений.

Ошибки встречаются разноплановые. Например, есть выход за границы массива:

Диагностика PVS-Studio: V557 Array overrun is possible. The value of ‘i’ index could reach 19. detectcharset. cpp 89

Хотя эта и подобные ошибки интересны, они не связаны с темой данной статьи. Поэтому, если интересно, можно посмотреть на некоторые другие ошибки в этом файле: mozilla-test. txt.

Вернемся к ошибкам в обработчиках ошибок

Я решил привести не парочку, а 10 примеров, чтобы убедить вас в актуальности проблем наличия дефектов в обработчиках ошибок. Конечно, обработчики ошибок не самые критичные и важные участки программы. Но ведь программисты их пишут, а значит, надеются с их помощью улучшить поведение программы. К сожалению, как показывают мои наблюдения, очень часто проверки и обработчики ошибок не работают. Смотрите, мне было достаточно одного, пусть и крупного проекта, чтобы показать множество ошибок данного типа.

Что же с этим делать и какие можно дать рекомендации?

Первая рекомендация

Нужно признать, что можно сделать ошибку даже в простой проверке. Это самое сложное и важное. Именно из-за того, что обработчики ошибок считаются простыми фрагментами кода, в них так много опечаток и иных дефектов. Обработчики ошибок не проверяют и не тестируют. На них не пишут тесты.

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

По статистике альпинисты чаще всего падают в конце подъема. Это случается не из-за усталости, а из-за того, что человек думает, что ему осталось совсем немного. Он расслабляется, теряет внимательность и, как результат, чаще допускает ошибки. С программистом при написании кода происходит что-то похожее. Он много сил и внимания тратит на алгоритм, а различные проверки пишет не сосредотачиваясь, так как уверен, что в них он не может допустить ошибку.

Итак, теперь вы предупреждены. И я уверен, это уже очень хорошо и полезно.

Если вы скажите, что подобные глупые ошибки допускают только студенты и неопытные программисты, то вы не правы. Опечатки легко делают все. Предлагаю на эту тему вот эту небольшую заметку «Миф второй — профессиональные разработчики не допускают глупых ошибок». Я могу подтвердить это множеством примеров из различных проектов. Но думаю, приведенных здесь вполне достаточно, чтобы задуматься.

Вторая рекомендация

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

Неработающий механизм сохранения дампа не только бесполезен, он только создает видимость, что в случае беды им можно будет воспользоваться. Если пользователь пришлёт испорченный dump-файл, то он не только не поможет, а может только ввести в заблуждение и на поиски ошибок будет потрачено больше времени, чем если бы dump-файла вообще отсутствовал.

Рекомендация выглядит простой и очевидной. Но у многих ли, из читающих эту заметку, есть юнит-тесты для проверки класса WriteMyDump ?

Третья рекомендация

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

Другими словами, при статическом анализе покрытие кода составляет 100%. Достичь такого покрытия кода с помощью других видов тестирования практически нереально. Покрытие кода при юнит-тестах и регрессионном тестировании обычно составляет менее 80%. Оставшиеся 20% протестировать очень сложно. В эти 20% входят большинство обработчиков ошибок и редких ситуаций.

Четвертая рекомендация

Можно попробовать использовать методологию внесения неисправностей. Смысл в том, что ряд функций время от времени начинают возвращать различные коды ошибок, и программа должна корректно их обрабатывать. Например, можно написать свою функцию malloc(), которая время от времени будет возвращать NULL, даже если память ещё есть. Это позволит узнать, как будет вести себя программа, когда память действительно кончится. Аналогично можно поступать с такими функциями, как fopen(), CoCreateInstance(), CreateDC() и так далее.

Существуют специальные программы, которые позволяют автоматизировать этот процесс и не писать самостоятельно свои функции, приводящие временами к отказу. К сожалению, я не работал с подобными системами, поэтому затрудняюсь рассказать про них подробнее.

Заключение

Дефекты в обработчиках ошибок встречаются часто. К сожалению, я не уверен, что приведенные в статье рекомендации достаточны, чтобы их избежать. Но я надеюсь, что вас заинтересовала эта проблема, и вы сможете придумать способы сократить количество недочетов в своих программах. Также я и другие читатели будут признательны, если вы поделитесь своими идеями и методиками, которые позволят избежать ошибки рассмотренного вида.

Источники:

https://java9.ru/?p=108

https://proglib. io/p/beginners-fails/

https://pvs-studio. com/ru/a/0078/

Понравилась статья? Поделиться с друзьями:
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: