Довольно много плохих вопросов, которые я вижу на StackOverflow, можно описать следующей формулой:
Вот моё решение домашнего задания. Оно не работает.
[20 строк кода]
И… всё.

Прим. пер.: это перевод статьи "How to debug small programs", на которую ссылаются в справочном разделе английского StackOverflow, посвящённом созданию минимальных, самодостаточных и воспроизводимых примеров. Мне кажется, она прекрасно описывает то, что должен знать каждый программист — основы отладки нерабочего кода.

Если вы читаете эту заметку, то, скорее всего, вы перешли по ссылке, которую либо я, либо кто-то ещё оставил под вашим вопросом на StackOverflow незадолго до того, как этот вопрос был закрыт и удалён. (Если вы читаете эту заметку по другому поводу, оставляйте свои любимые советы по отладке маленьких программ в комментариях).

StackOverflow — это сайт вопросов и ответов, но для очень конкретных вопросов про конкретный код. «Я написал какой-то баганутый код, который не могу исправить» — это не вопрос, это история, причём не слишком интересная. «Почему при вычитании единицы из нуля я получаю число, большее нуля, из-за чего моё сравнение с нулём в строчке 12 некорректно вычисляется в true?» — вот конкретный вопрос про конкретный код.

Итак, вы просите интернет починить вашу сломанную программу. Наверняка вам никогда не рассказывали, как отлаживать маленькие программы, потому что — внимание — то, что вы сейчас делаете — не самый эффективный способ. Сегодня отличный день, чтобы научиться отлаживать код самостоятельно, потому что StackOverflow не будет этим заниматься вместо вас.

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

Для начала включите все предупреждения компилятора. Нет ни одной причины, по которой корректная программа в 20 строк кода могла бы получить предупреждение компилятора. Предупреждение — это когда компилятор говорит вам: «это программа компилируется, но делает не то, что вы думаете», и поскольку это именно та ситуация, в которой вы находитесь, вам следует к нему прислушаться.

Прочитайте предупреждения очень внимательно. Если вы не понимаете, почему было выдано то или иное предупреждение — это хороший вопрос для StackOverflow, потому что это конкретный вопрос о конкретном коде. Добавьте в вопрос точный текст предупреждения, точный код, на котором оно было выдано, и точную версию компилятора, который вы используете.

Если в вашей программе всё ещё есть баг, найдите резиновую уточку. Если резиновой уточки рядом не оказалось, найдите другого студента-программиста — между ним и уточкой нет почти никакой разницы. Объясните уточке простыми словами, почему каждая строчка каждого метода в вашей программе очевидно верна. В какой-то момент вы столкнётесь с трудностями: либо вы не понимаете метод, который написали, либо в нём ошибка, либо и то, и другое. Сконцентрируйтесь на этом методе; скорее всего, проблема именно в нём. Нет, правда, метод утёнка работает. И, как легендарный программист Рэймонд Чен добавил в комментариях ниже (прим.пер.: к исходному посту): если вы не можете объяснить уточке, зачем нужна та или иная строчка — может, это потому что вы начали писать код до того, как у вас появился план.

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

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

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

Если баг всё ещё никуда не делся, научитесь писать утверждения (assertions), чтобы проверить пред- и постусловия. Утверждение — это почти как комментарий, только оно сообщает вам, когда условие нарушается; а нарушенное условие — это почти всегда баг. В C# вы можете сказать using System.Diagnostics; в начале кода, а в середине написать Debug.Assert(value != null); или что-то аналогичное. В каждом языке есть механизм для формулирования утверждений; попросите кого-нибудь рассказать вам, как их использовать в том языке, на котором пишете. Напишите утверждения для предусловий в начале метода, а утверждения для постусловий — перед строчкой, в которой метод завершается. (Конечно, это проще всего сделать, когда в каждом методе есть ровно одна точка возврата.) Теперь, если вы запустите программу и какое-то утверждение окажется неверным, вы узнаете, в чём проблема, и это будет несложно отладить.

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

Наконец, если в вашей программе всё ещё есть баг, напишите на бумажке точное описание действия, которое вы ожидаете от каждой строчки кода при запуске на вашем примере. Ваша программа занимает всего 20 строк. У вас должно получиться написать вообще всё, что она делает. Теперь пройдитесь по коду при помощи отладчика, проверяя каждую переменную на каждом шаге и строчка-за-строчкой сверяя поведение вашей программы с бумажкой. Если программа делает то, что не написано на листочке, то ошибка либо на листочке (в этом случае вы не понимаете, что делает ваша программа), либо в программе (в этом случае вы написали что-то неправильно). Поправьте то, что неправильно. Если вы не знаете, как это исправить — у вас как минимум появился конкретный технический вопрос, который вы можете задать на StackOverflow! В любом случае повторяйте процесс до тех пор, пока ваше описание поведения программы и реальное поведение программы не совпадут.

Пока вы работаете в отладчике, я призываю вас обращать внимание на малейшие сомнения. Большинство программистов естественным образом полагают, что их код работает, как ожидается, но ведь вы отлаживаете его ровно потому, что это не так! Много раз я отлаживал код и краем глаза замечал быстрое мелькание в Visual Studio, которое означало «эта память только что была изменена», а я знал, что эта память вообще никак к моей проблеме не относится. Так почему она изменилась? Не игнорируйте эти придирки, изучайте странное поведение до тех пор, пока вы не поймёте, почему оно либо корректное, либо некорректное.

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

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

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

Комментарии (86)


  1. Fox_exe
    14.10.2017 22:08

    «Накипело»?
    Или реальная попытка заставить людей думать и гуглить, прежде чем задавать вопрос?


    1. yeputons Автор
      14.10.2017 22:12

      Зачем писал исходный автор — не знаю. Я решил перевести, потому что тут перечислены практически все стандартные ответы на фразу «оно не работает». Более того — не только перечислены, но и описано, почему то или другое надо попробовать, а также как именно это надо пробовать.
      Скажем, если человек не знает, что полезно делить программу на кусочки в процессе отделки, то ему можно кинуть ссылку на эту статью.


      1. Jef239
        15.10.2017 05:16

        Двух нету: :-)

        1. Баг в компиляторе.
        2. Баг в процессоре.

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

        Как думаете, сколько времени тупил программист, когда у него 2.0 == 16.0 выдавало true? Угу, производитель ЭВМ потом тоже сильно удивлен был, ибо умудрились забыть сравнение ординат (экспонент) для вещественных чисел.


        1. netch80
          15.10.2017 09:55
          +12

          Тут всё-таки лучше быть практичным и не рассматривать события с очень малой вероятностью — а именно таким сейчас можно считать ошибку в процессоре или компиляторе. Очень малой — это меньше миллионной того, что баг у автора кода. В 50-е, да, было иначе, но сейчас не 50-е.

          Кстати, уже чисто интересно с исторической точки зрения — где и когда существовала терминология, что порядок вещественного числа назывался «ординатой»? Я такого не нагугливаю, хотя времена, когда ЭВМ были большие, ещё застал лично.


          1. atrosinenko
            15.10.2017 12:04
            +5

            <sarcasm> Разработчики компилятора OCaml тоже так думали </sarcasm>
            А если серьёзно, то наивно, конечно, ожидать, что вот только начал программировать, написал 20 строчек кода и словил глюк процессора. Но не удивлюсь, если новичок, напоровшийся на undefined behavior в C, будет уверен, что наткнулся на баг в компиляторе, который неправильно компилирует.


            1. atrosinenko
              15.10.2017 12:50
              +2

              Если кто не видел эту чудесную историю. Но тут уж и близко не новичок, конечно.


            1. netch80
              15.10.2017 13:26
              +1

              > Разработчики компилятора OCaml тоже так думали

              Ну так и я находил баг в GCC (репорт принят и подтверждён), но речь-то (без сарказма) о вероятности каждого конкретного случая…

              > Но не удивлюсь, если новичок, напоровшийся на undefined behavior в C, будет уверен, что наткнулся на баг в компиляторе, который неправильно компилирует.

              А так и происходит. В трекере GCC при отсылке нового тикета сразу пишут — «если ваша программа начинает работать при -fwrapv, ищите ошибку у себя», а количество закрытых на undefined behavior тикетов идёт на тысячи.


              1. Jef239
                15.10.2017 15:05
                -2

                И сколько собственных багов пришлось на каждый багрепорт в GCC? Неудели и вправду миллионы?

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

                А объяснять новичкам, что это не баг в компиляторе, а баг в прокладке между экраном и клавиатурой — каждому приходилось. И не один раз. Не об этом речь.


                1. netch80
                  15.10.2017 18:06

                  > И сколько собственных багов пришлось на каждый багрепорт в GCC? Неудели и вправду миллионы?

                  Ну во всяком случае больше тысячи. Как-то не считал всю длину своей карьеры, даже если ограничивать C. :)

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

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

                  Но — я при этом имел достаточно общего опыта, чтобы поймать и описать баг. А новичок — таким навыком не владеет. Собственно, исходная статья именно на формирование такого навыка и направлена. Если он после этого всего будет всё равно уверен, что баг не у него — тогда имеет смысл смотреть кому-то существенно опытному.

                  > А объяснять новичкам, что это не баг в компиляторе, а баг в прокладке между экраном и клавиатурой — каждому приходилось. И не один раз. Не об этом речь.

                  Именно что исходная статья задаёт именно «это», о котором речь. И я стараюсь держаться в этих рамках.


                  1. Jef239
                    15.10.2017 19:51

                    Ну если багом компилятора считать internal complier error, то скорее десятки, чем тысячи. Другой момент, что проще научиться обходить баг, чем его репортить (особенно в микрософт).

                    Новичок новичку рознь. У нас человек попросился на практику (учится в тезникуме). Ну коллега ему и дал стыковку проца с SDRAM (другой проц, чем в примере выше). А в стыковках микросхем очень много такого, что называется «баг документации». То есть из 10 способов сделать в соответствии с докой — будут работать 1-2. А остальные — не будут. И багом это никто не считает. Обычное дело — что-то не рассказано в доке.

                    Такого очень много в тех же GPS-чипах или в GSM-модемах. И новичку надо понимать, что неописанных в документации особенностей — дофига.

                    И надо не искать баги (хотя это полезно), а искать способ работы несмотря на баги и лакуны в доках.

                    А meassge такой: не надо считать, что баг в железке, документации, процессоре, компиляторе — это редчайшая экзотика. Как только вы от области, где пасутся миллионы пользователей переходите туда, где пользователей всего лишь сотни — баг становится весьма вероятен.

                    В подтверждение — описание эпохального бага в ИКД ГЛОНАСС (от авторов формата RINEX)

                    К сожалению, в интерфейсном документе ГЛОНАСС, в одной из формул была допущена ошибка в знаке.
                    Значения должны быть записаны в RINEX файл как -TauN, +GammaN, -TauC.
                    Первоначальное определение требовало указания -TauN, -GammaN, +TauC. См. параграф 8.2.


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


                    1. mayorovp
                      15.10.2017 21:14
                      +2

                      Почему вы уверены что все вокруг только делают что ковыряются с железками? Большинство новичков либо клепает сайты либо пишет мобильные приложения (прикладные!). Даже десктопные приложения — уже не в тренде.


                      И вот тут-то натолкнуться на баг в процессоре уже очень тяжело.


                      1. Jef239
                        15.10.2017 22:46

                        Как будто у меня глобус другой. На моем глобусе, чтобы нарваться на баг — даже программистом быть не надо.

                        Не видели как мобильники виснут? Это не баг софта, это или банальная трещина в многослойной плате или окислились дорожки из-за попадания влаги.

                        Чтобы нарваться на баг в браузере — не надо быть программистом, достаточно просто много серфить. Падает и IE и FireFox и Chrome. Не так часто — но падает. Разок было не лень — кинул в багзиллу по FireFox, исправили.

                        А уж веб-разработка… Мама мия!!! Не, я, конечно, полный чайник в вебе. Делали мы web-интерефейс к одной железке, ну и кроме своего кода пришлось ещё и повозиться с отладкой сайта. Ну как бы проще самому исправить, чем пинать фрилансера.

                        Уж не знаю, баги это или не баги, но из десятка способов сделать одно и то же, во всех браузерах одинаково работал один. Остальные — или дают артефакт в одном из браузеров или вообще не работают. Самый норовистый — это, разумеется, был IE 8. Повезло — отспорили, что IE 7 не будет. :-)

                        Так что баг «в процессоре», знаком почти всем, у кого хватило денег на мобильник. Баги в софте — все, кто активно серфит. А вот баги в коде — только для программистов.


                        1. mokhin-denis
                          17.10.2017 13:40

                          >но из десятка способов сделать одно и то же, во всех браузерах одинаково работал один

                          Ну тут скорее вопрос о соблюдении тем или иным браузером соглашений по HTML, CSS, JS… если я вас правильно понял. А сделать кроссбраузерную штуку… да еще и на несколько версий… вы, батенька, знаете толк))


                          1. Jef239
                            17.10.2017 14:52

                            Сильно раздражает, когда два часа пытаешься настроить железку через web-интерфейс, а потом выясняется, что она некорректно работает из современного Chrome, ибо сделана под FireFox.

                            Так что только кросброузерно. И ещё желательно угадать, чтобы хотя бы через 10 лет не перестала работать. Радиопередатчики — они такие, и по 20-30 лет живут. Почему наш интерфейс должен сдохнуть первым?


                            1. mokhin-denis
                              17.10.2017 15:01

                              Тут, я думаю, не нужно тогда полагаться на браузер, ибо он получает ДАННЫЕ+ПРАВИЛА для их отображения, и интерпретирует эти правила сам, кто во что горазд… Вот и выходит, что выходит. Получается надо брать ДАННЫЕ в сыром виде (что-то типа json), и интерпретировать и показывать их самому. Тогда это очередной велосипед-браузер.


                              1. Jef239
                                17.10.2017 15:17

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


                  1. zagayevskiy
                    15.10.2017 21:52

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


          1. Jef239
            15.10.2017 14:58

            Чтобы понять, что в современных процессорах полно багов — достаточно разок почитать errata. Вот вам на SoС, а вот на процессор.

            Из недавнего от коллеги. Не проходит инициализация ОЗУ. 10 раз проверили пайку и код. Спаяли плату с другим ОЗУ по тому же стандарту JEDEC — работает. И пара реплик на форумах от американцев, что не только у нас так. Ну видимо будет в errata лишний раздел. :-)

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

            Кстати, я бы добавил к инструментам отладки осциллограф, логический пробник и микроскоп (пайку проверять). Не так уж и редко приходится использовать. :-)

            Баг в компиляторе — это, конечно, редкость. Но — бывает. В любом случае, когда компилятор вылетает во время компиляции — это баг. В компиляторе, в конкретном порте, в библиотеке, в процессоре (!!!!) — надо разбираться. Был один интеллевский процессор с отбитым уголком — очень интересно себя вел. На тестах — все хорошо, а в реальной работе иногда падал.

            Скажем, если брать современные андроиды, то большинство их вылетов и зависаний — это трещины на плате, а не баги в коде.

            Ордината (вместо порядка) — это термин из школьного курса математики примерно 1980ого года. Связан был скорее всего с использованием лографимической и полулографимической бумаги. А что не нагугливаете — странно. Вот вам раз, два, три, четыре, пять, шесть, семь. Забавно, что для десятичной записи меня тоже тянет использовать слово «порядок», а вот для двоичной «ордината».

            Очень малой — это меньше миллионной того, что баг у автора кода.

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


            1. netch80
              15.10.2017 18:26

              > а вот на процессор.

              «This document is only available in a PDF version to registered ARM customers.» Спасибо, но «кортинки не грузяццо» ©.
              Впрочем, идею я понял. Как верно заметил yeputons@, Вы таки «страж ночи». И взгляд соответствующий.
              Но я таки уверен, что автор исходной статьи подразумевал тех, кто в самом крайнем случае ищет проблемы в том, что он написал под эмулятор MIPS для университетского курса (таких валом валит в SO /assembly), а скорее всего под банальный x86, и скорее всего даже не под C. И там названные мной цифры вероятности более справедливы.

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

              Именно. И они за пределами контекста.

              > Ордината (вместо порядка) — это термин из школьного курса математики примерно 1980ого года. Связан был скорее всего с использованием лографимической и полулографимической бумаги. А что не нагугливаете — странно. Вот вам раз, два, три, четыре, пять, шесть, семь.

              Я гуглил фразой «ордината вещественного числа» и её вариациями. Наверно, если бы ввёл «мантисса и ордината», нашлось бы. Тут, как обычно, надо заранее знать половину ответа, чтобы правильно задать вопрос :)
              Что курс района 80-го года — показывает, почему я уже такого не знал — я в 80-м только в 1-й класс пошёл :) а где-то через пару лет была заметная мутация программ и учебников.


              1. Jef239
                15.10.2017 20:02
                -1

                я не страж ночи — я тот редкий зверь, которому нравится отлаживать чужой код. :-)

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


          1. Jef239
            15.10.2017 15:48

            А пока все дружно минусы ставили, я отписал автору очередной баг в микросхеме GPS-приемника. :-) Ну что поделать, работа у меня такая.


            1. yeputons Автор
              15.10.2017 15:53

              О, да вы почти (или не почти) страж ночи.


            1. zagayevskiy
              15.10.2017 22:02

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


              1. Jef239
                15.10.2017 22:51

                А что, у новичков другое железо? Иди другой софт? Или новички супераккуратные люди, и в отличие от остальных на баги не нарываются?


                1. zagayevskiy
                  15.10.2017 22:53

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


                  1. Jef239
                    15.10.2017 23:10

                    Ваши родители код пишут? А зависший мобильник они хоть раз видели? Вот вам и разница «в миллион раз». :-)

                    P.S. Зависший мобильник — это, как правило, баг железа. Трещина в плате или окислившиеся дорожки из-за попадания влаги.


                    1. zagayevskiy
                      15.10.2017 23:16
                      -2

                      %)
                      Ты реально тупой или придуриваешься? Речь идёт о "неправильном" поведении только что написанного новичком кода, который объясняется багом в железе.
                      Утверждение о зависшем мобильнике видится мне как минимум сомнительным. Все зависания, с которыми я лично сталкивался, лечились перезагрузкой.
                      Пожалуй, в очередной раз попрошу пруфы на статистику.


                      1. AntonAlekseevich
                        15.10.2017 23:31

                        Ну да почистили память и проблема решена. Костыль, надо исправлять! (Уровень JVM/Native(Если запущенно из /system/bin) если Android либо телефон на JEEMP{Точное название не помню}, если виснет телефон с WP/W10M(Уровень ОС) то это уже нехватка памяти либо ОСь шалит, если проявляются глюки на экране или происходит ересь на аппарате то случай Jef239 (Это редко когда происходит), вирусня после прошивки{Да есть такие телефоны} производитель залил значит{Такой телефон на свалку сразу.}, вроде все варианты которые могу перечислить.)


                      1. Jef239
                        15.10.2017 23:43

                        Это дико зависит от области разработки. Кроме примеров по стыковке с хитрой микросхемой (баги железа), вот вам тот же вопрос в иной постановке.

                        Какие шансы, что написанный новичком сайт будет одинаково работать во всех браузерах?

                        Да почти нулевые. Обязательно где-то что-то не реализовано, где-то что реализовано не так, где-то что-то задокументировано не так, как реализовано…

                        Только если писать на C++ в рамках учебных программ на одном компиляторе — шансы налета минимальны. А на том же С++ как сразу 7 операционок и 7 компиляторов — так сразу и опаньки. Тут так отступили от стандарта, тут эдак…

                        Вот именно, что зависания лечатся перезагрузкой, а не обновлением прошивки. И никакое обновление от них не спасает. Зависания — это прежде всего плохой контакт. На переходах между слоями платы, на стыке к процессора (тот самый реболлинг), на самих дорожках (окисление от воды). А на исправном железе — это долго надо потрудиться, чтобы тот же андроид завесить. я не уверен, что это вообще возможно.


                        1. AntonAlekseevich
                          16.10.2017 00:57

                          А на исправном железе — это долго надо потрудиться, чтобы тот же андроид завесить.

                          Трудиться сильно не нужно, нужно только заставить приложение выдавать мусор в большом объеме, тут тебе и зависшее устройство и плата в порядке. (Простой пользователь до этого может додуматься, но не станет этого делать.)


                          Какие шансы, что написанный новичком сайт будет одинаково работать во всех браузерах?

                          А если новичок не использует CSS, JS, а только HTML 4.01 Strict?
                          Он то точно должен отображаться везде одинаково нормально.


                          Зависания — это прежде всего плохой контакт.

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


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

                          А если аппаратная перезагрузка или выключение не предусмотрено либо не желательно?


                          1. Jef239
                            16.10.2017 01:26

                            А если новичок не использует CSS, JS, а только HTML 4.01 Strict?
                            Он то точно должен отображаться везде одинаково нормально.
                            Если strict, а не transitional — значит уже не новичок. А отображаться одинаково — должен, вот только кому он задолжал? Явно не мне. :-)

                            По личному чайниковскому опыту, создать сайт, который отображается везде одинаково — это высокое искусство, доступное лишь профессионалам.

                            А если аппаратная перезагрузка или выключение не предусмотрено либо не желательно?

                            В таком случае делают разного рода watchdog, перезагрузку одного процессора другим и так далее. Ну в общем можно сделать достаточно неубиваемо, даже несмотря на баги в коде.


                            1. AntonAlekseevich
                              16.10.2017 10:01

                              Если strict, а не transitional — значит уже не новичок.

                              По заданию для новичков дают strict чтобы освоились, на transitional сами потом ковыляют переходят.


                              По личному чайниковскому опыту, создать сайт, который отображается везде одинаково — это высокое искусство, доступное лишь профессионалам.

                              Да это искусство, но не только доступное лишь для профессионалов, просто нужно время.




                              В таком случае делают разного рода watchdog, перезагрузку одного процессора другим и так далее.

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


                              Ну в общем можно сделать достаточно не убиваемо, даже несмотря на баги в коде.

                              Так можно сделать, но нужно время.


          1. F0iL
            16.10.2017 12:50

            Из недавнего:
            Код

            std::timed_mutex mutex;
            mutex.lock();
            mutex.try_lock_for(std::chrono::seconds(1));
            

            почему-то не ждал, как полагается, секунду для разблокировки мьютекса, а сразу говорил что таймаут истёк и блокировку снять не удалось.
            На это была убита почти половина дня, а в итоге выяснилось вот это вот:
            gcc.gnu.org/bugzilla/show_bug.cgi?id=54562


            1. F0iL
              16.10.2017 12:57

              Это я к тому, что код элементарный, и часто его можно встретить даже в примерах а-ля «hello world с тредами». И несложно представить, какой разрыв мозга случится у разработчика-новичка, когда три простые строчки не будут работать как надо из-за бага в стандартной библиотеке.


        1. lany
          16.10.2017 03:55


          1. Jef239
            16.10.2017 04:02

            Ну 35 лет назад я тоже думал, что баг в компиляторе или железе — это нечто особенное. Когда я в 17 лет впервые свалил компилятор — я тоже сам себе не верил. «Как это, простая опечатка — а компилятор падает. Неужели и в компиляторах бывают баги?» С тех пор — опыта как-то добавилось. И прежде всего — по умению обходить баги компиляторов и железа.


            1. lany
              16.10.2017 04:05

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


              1. Jef239
                16.10.2017 04:21

                От сегмента зависит. Если что-то используют сотни тысяч людей — вряд ли там есть баг. А если ты в первом десятке, кто использует API — багов там найдется немерянно. Ну хотя бы вида «может оно так и задумывалось, но какого черта не описали?!».

                В любом случае, при стыковке с разными микросхемами/железками проблема не в том, как бы найти баг. Проблема в том, чтобы найти путь, на котором устройство работает без багов.

                А своих собственных багов — полно у всех, это не новость. Мне об этом компилятор каждый день намекает.

                Очень забавно, что «баг» нашелся в стандарте С++.

                Не могу удержаться от цитаты...
                в синтаксисе есть неоднозначности. Это надо оценить: в Стандарте (!) языка программирования прямо написано, что некоторые конструкции можно трактовать двояко — либо как объявление, либо как оператор!

                В несколько упрощенном виде формулировка из стандарта выглядит так:
                «выражение, содержащее в качестве своего самого левого подвыражения явное преобразование типа, которое записано в функциональном стиле, может быть неотличимо от объявления, в котором первый декларатор начинается с левой круглой скобки». Классический пример: что такое T(a); если T — некоторый тип? С одной стороны, это как бы объявление переменной с именем a, тип которой задан как T. С другой — конструкцию можно трактовать как преобразование типа уже объявленной где-то ранее переменной a к типу T.


                1. lany
                  16.10.2017 06:24

                  Ну вы же понимаете, что статья не о тех случаях, о которых вы говорите? :-)


                  1. Jef239
                    16.10.2017 07:18

                    Гм, вы думаете, что со всякими ардуино и малинкой возятся бородатые гуру со стажем в embeded 20 лет? Тогда удивлю: их уже школьникам преподают. А шансы нарваться на баги железа — как во взрослом embeded.

                    БОНУС: никому не интересная история, как я сам был в роли чайника на JS
                    Уже писал, что пару лет назад я был как раз в роли чайника — было проще самому править баги в веб-интерфейсе, чем просить фрилансера (собственно моя часть бала сишная). Ну в общем сделать так, чтобы работало во всех браузерах — это была такая боль… IE8, IE9, IE10, Chrome, FireFox, Opera, Safari… Причем постоянно — работает везде, кроме одного. Причем этот один — все время разный.

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

                    Ладно, отладился, повезли ставить. А там оказалась чуть более старая версия IE8. И никаких шансов на обновление — ибо береговая радиостанция морской связи. Ну в общем заработало почти всё. Но не всё. :-)

                    Впрочем, мытарства JavaSript-девелопера, который захочет написать переносимую программу на Си — я вполне себе представляю. Хрен редьки не слаще.


                    1. Areso
                      16.10.2017 12:50

                      Школьники и Ардуино… У меня это хобби и Ардуино. Не так давно натолкнулся на забавную фичу (хотя мне было невесело, да), которая касается «наводки» с одного ШИМа на другой:
                      arduino.stackexchange.com/questions/34501/problem-with-pwm-interference


  1. Sdima1357
    14.10.2017 22:10

    Почему то вдруг вспомнилось
    Где то в 2006 году когда у меня был отлажен алгоритм на GPU для которого нужен был работающий X server, американский коллега (тоже программист с большим стажем) из моей же компании спрашивает " а как запустить эту программу X?". Я ему ответил чтоб принципе достаточно набрать «X (uppercase ) а затем „enter“


  1. third112
    14.10.2017 22:20
    +1

    Нет ни одной причины, по которой корректная программа в 20 строк кода могла бы получить предупреждение компилятора.


    Есть: «переменная нигде не используется».

    разбить код на методы поменьше


    20 строк кода разбить код на методы поменьше? Сколько методов м.б. в программе в 20 строк?!

    Сегодня отличный день, чтобы научиться отлаживать код самостоятельно, потому что StackOverflow не будет этим заниматься вместо вас.


    Будет! И до StackOverflow (и кроме) этим занимаются много добрых людей (и я в том числе) — 20 строк кода в знакомой мне задаче — полная ерунда. Специально не ищу, но если случайно попалось на глаза и сразу вижу ошибку, то почему не помочь? Тем более, что на некоторых сайтах за такую помощь плюсуют — мне приятно, а человеку польза…

    Поэтому: не бойтесь спрашивать по существу!


    1. yeputons Автор
      14.10.2017 22:28

      Есть: «переменная нигде не используется».

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

      20 строк кода разбить код на методы поменьше? Сколько методов м.б. в программе в 20 строк?!

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

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


      +1

      Поэтому: не бойтесь спрашивать по существу!


      +1, но отлаживать самостоятельно тоже хорошо бы научиться. Мне кажется, если всё сваливать на добрых людей, то непонятно, как они находят самые коварные баги, хотя там всё более-менее алгоритмизированно (по модулю знаний о специфических подставах языка). Так что тут нужна, прошу прощения, «золотая середина».


      1. third112
        14.10.2017 23:11
        +1

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


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

        В крайнем случае: одна строка — один метод.


        Зависит от языка и от принятых форматов («елочка») в этом языке. У меня простейшие методы больше занимают:

         procedure TGraph.addEdge (v,u : integer);
        begin
           addEdgeA (v,u);
          public
            procedure addEdge (v,u : integer); // добавить ребро (v,u)
        end;


        + определение класса:

        TGraph = class(TObject)
          protected
            procedure addEdgeA (v,u : integer); virtual; abstract;
          end;


        но отлаживать самостоятельно тоже хорошо бы научиться.


        Полностью согласен. Обязательно надо научиться.

        по модулю знаний о специфических подставах языка). Так что тут нужна, прошу прощения, «золотая середина».


        Ok. И не только языка, но и библиотек. Можно проработать 10 лет и хорошо уметь отлаживать самостоятельно, но подключаешь новую библиотеку, которую не использовал, т.к. не было задачи, и оказываешься новичком.


        1. third112
          14.10.2017 23:18

          Листинги перекосило:

          Первый:

          procedure TGraph.addEdge (v,u : integer);
          begin
             addEdgeA (v,u);
          end;


          второй:

          TGraph = class(TObject)
            protected
              procedure addEdgeA (v,u : integer); virtual; abstract;
            public
              procedure addEdge (v,u : integer); // добавить ребро (v,u)
            end;


          (Почему на Хабре так мало времени на редактирование? -Инет бывает ооооочень задумчив!)


        1. mayorovp
          15.10.2017 11:37
          +1

          Работе-то лишние переменные не мешают — а вот на MCVE программа с лишними переменными не тянет.


      1. Antervis
        15.10.2017 14:24
        +2

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

        unique_lock<mutex> lock(_mutex);


        1. zagayevskiy
          15.10.2017 22:05

          И что, компиляторы ругаются?


          1. Antervis
            16.10.2017 05:50

            признаться, не наблюдал чтоб компиляторы ругались ворнингами на локи. А вот syntax highlighter'ы ругаются


    1. TheDeadOne
      15.10.2017 08:22
      +6

      то почему не помочь?

      Медвежья услуга.


  1. geher
    14.10.2017 22:33

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


    1. third112
      14.10.2017 23:26

      Например, такую пошаговую отладку:

      for i:=1 to 1000000 do
       a[random(1000000)] := a[random(1000000)] ;


      ;)


      1. geher
        15.10.2017 20:14

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


    1. lany
      16.10.2017 04:43

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


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


      1. geher
        16.10.2017 09:04

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


  1. rokobungi
    14.10.2017 23:20

    Интересно, сколько методов может содержать программа из 20-ти строчек? Хороший челлендж, написать программу в 20 строчек с максимальным количеством методов, при этом делающую что-то осмысленное.
    Вообще статья полезная.
    PS При чтении вспомнилась старая байка: если вы с первого раза сумели написать программу, в которой компилятор не обнаружил ни одной ошибки, сообщите об этом системному программисту. Он исправит ошибки в компиляторе.


    1. andreymal
      15.10.2017 01:00

      написать программу в 20 строчек с максимальным количеством методов

      https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpriseEdition? :)


  1. michael_vostrikov
    15.10.2017 07:30

    напишите спецификации, примеры, тесты, предусловия, постусловия и утверждения для каждого метода перед тем, как писать сам метод

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


  1. TheDeadOne
    15.10.2017 08:25
    +7

    На русском SO и Тостере нередко встречается ещё более весёлый вариант вопроса:

    Есть задача, но сделать почему-то не получается! Помогите!!!
    [0 строк кода]


    1. san-x
      15.10.2017 12:12

      Даже еще лаконичнее встречаются:

      Есть задача.
      [всё, на этом конец]


    1. dee3mon
      15.10.2017 14:17

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


      1. Areso
        16.10.2017 09:20

        Давно не студент, но делаю точно также — ищу код, который делает тоже самое или близкое к тому, что мне надо, потом адаптирую решение. И да, чужой код не всегда понятен.
        А особенно, если чужой код из 5 строчек с вызовом внешней библиотеки. Как должна себя вести библиотека на зоопарке разных ОС, с разными аудиодрайверами (PulseAudio, Alsa) и разным набором установленных программ — заранее неизвестно.


        1. Zverik
          16.10.2017 12:19
          +1

          Вы пропустили важный шаг: ищете код, понимаете его и адаптируете решение.


  1. synedra
    15.10.2017 09:24
    +2

    Не хватает ещё одного совета, ИМХО. Убедитесь, что вы не занимаетесь преждевременной оптимизацией и не используете при этом фичи, которые не понимаете. Классический пример на питоне:

    >>> a = 2
    >>> b = 2
    >>> a is b
    True
    >>> a = 1e10
    >>> b = 1e10
    >>> a is b
    False
    


    Сам так тупил в процессе изучения питона. Я знал, что сравнение объектов требует вызова __eq__(), что относительно долго, и что для int меньше какого-то порога (который я и сейчас не помню) is всегда True, когда == True. Ну ведь абсолютно логично и безопасно допустить, что в условном физбаззе значения рассматриваемого числа не выйдут за этот самый порог. Можно сэкономить пару-тройку тактов.


    1. AntonAlekseevich
      15.10.2017 10:54

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


    1. nanopony
      15.10.2017 12:12
      +2

      Причина немного в другом. Python всегда держит все целые от -5 до 256, поэтому a и b по сути действительно будут ссылаться на один и тот же объект, который был преаллоцирован при запуске и is выдаст True. Убедиться можно вызвав id(a) и id(b). 1e10 же действительно надо создать.


      1. ALexhha
        15.10.2017 20:37

        Ну не знаю, на python 2.7 это не так

        a = 2
        b = 2
        print id(a), id(b)
        print a is b
        
        a2 = 1e10
        b2 = 1e10
        print id(a2), id(b2)
        print a2 is b2

        На выходе получаю

        31424480 31424480
        True
        31485288 31485288
        True


        1. AntonAlekseevich
          15.10.2017 21:02

          В ранних версиях интерпретатора Python 2/3 возможно был такой баг


          a = b = 1e10 
          a == b # False

          Когда мы ждем True


          Но в последних версиях такого я пока не встречал.


          1. ALexhha
            15.10.2017 21:28

            Я проверял на 2.7.14, Win 7 Pro.


            1. AntonAlekseevich
              15.10.2017 21:38

              Я говорю что раньше был такой баг, а не словил его только что.


              Кстати раз указали версию Python
              Python 2.7.14
              Python 3.6.2
              ArchLinux rolling


              Ловил этот баг я на версии: Python 2.6.xx
              Номер патча не помню, но тогда использовал тот же дистрибутив.


        1. Civil
          15.10.2017 22:23

          Поведение отличается в интерактивном режиме и при работе скрипта. В случаи с 1e10 — в интерактивном режиме будет False, в скрипте — True. Справедливо для 2.7.14 и 3.4.6 как минимум, но вероятно и для 3.5/3.6 будет таким же.


          1. nanopony
            15.10.2017 23:17

            Кстати, подобного поведения (id(a2)==id(b2)) можно добиться и в интерактивном режиме, достаточно обернуть этот код в функцию. Тогда он просто при подготовке байткода соберет все константы (их можно посмотреть в структуре function_name.__code__.co_consts) и соптимизирует аллокацию. С запуском скрипта, скорее похожая процедура, но уже на уровне модуля.


        1. netch80
          15.10.2017 22:25

          (del, по сути подтверждаю, что в интерактивном id различаются, из скрипта — нет)


  1. sitev_ru
    15.10.2017 12:02

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


  1. Smeilz1
    15.10.2017 12:12
    -3

    Неэффективно тратить большое количество времени на поиск ошибок, если есть технологии поиска ошибок в сети.
    Я думаю, что если невозможно найти ошибку за 15 минут, то стоит прибегнуть к помощи профи.


    1. zagayevskiy
      15.10.2017 22:13

      То есть, стать профи самому планов нет?


  1. VladlenBronislav
    15.10.2017 21:40

    Вопрос по фраза «включите все предупреждения компилятора» — в С++ есть несколько уровней компиляции? (вообще, есть такое понятие уровни компиляции?)


    1. yeputons Автор
      15.10.2017 21:41

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


      1. Civil
        15.10.2017 22:28

        Для gcc большую часть предупреждений включит сочетание "-Wall -Wextra" и опционально можно добавить "-pedantic" (будет еще ругань на использование gccизмов). Еще хорошей идеей можно считать включение в дебаг-сборках всяких ubsan (undefined behavior sanitizer), asan (address) и tsan (threads), но к сожалению можно включать только 1 за раз.


        1. Jef239
          15.10.2017 23:47

          Санитайзерам нужна поддержка на уровне библиотеки? Они с NewLib работают?


          1. Civil
            16.10.2017 01:37

            Хм, честно говоря я вопросом этим никогда не задавался, но кажется что не нужна. Это отдельные библиотеки с которыми, в том числе, надо линковаться. Если они с NewLib соберутся, то должны работать.


  1. Lorys
    15.10.2017 22:41

    Почти все советы из книги Программист-Прагматик, причём, кажется даже в том же порядке, в каком и даются эти советы в главах… Странное совпадение, просто как раз сейчас читаю эту книгу)


  1. mayorovp
    16.10.2017 08:39

    Спасибо за перевод. Поставили на него ссылку в справке ru.SO


  1. Mishootk
    17.10.2017 22:26

    Очевидно напрашивается следующий вывод: работающая маленькая программа на 20 строчек — это часть более крупной программы из 20 строчек. Когда разработчик овладевает искусством писать 20-строчные программы без ошибок, он овладевает искусством писать любые программы.


  1. XAHOK
    18.10.2017 13:08

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

    В работе чаще всего сталкиваюсь с обратным:

    Первое правило программиста: Твоя программа всегда содержит ошибки и работает не так, как ожидается.

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

    Третье правило программиста: Если твоя интуиция кричит, что там есть баг, то он там 100% есть, ибо см. правило №1. И ты этот баг уже нашел, но еще не осознал в чем именно он заключается.