Блог им. Replikant_mih

Кто как дебажит код?

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

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

★2
74 комментария
Дважды вычитываю каждый логически обособленный кусок кода (процедура, функция): первый раз — сразу по окончанию его написания, пока свежа в памяти вся логика и подразумеваемая функциональность, второй — когда заканчиваю работу над основной задачей, частью которой является данный блок — помогает против ошибок, когда скопипастил что-то, но поменял не все что нужно было.
 Иногда практикую первый проход новой задачи делать в режиме пошагового исполнения с контролем значений, но, как правило, предпочитаю внимательно вычитывать код несколько раз (больше двух) — перерасход затраченного времени все равно окупается против ситуаций, когда просто пытаешься выявить, что же работает не так.

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

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

avatar
_sg_, я не шарю, но юнит-тесты это вроде немного про другое, про то, чтобы не сломался работающий код после изменений в нем или сбоку от него, так? — а я пока написал про этап чтобы код вообще заработал))
avatar

тестирую в демо квике

написал часть — запустил — проверил-пишем дальше

практически к каждой строке коментарий

запись в файл выполнение каждой функции, максимум записей 

 

пишу на луа для квика, вернее пытаюсь)

avatar
Igr, ну да, логами покрыть — вариант — просто многие функции делают что-то сложное, а не просто циферку возвращают — ну т.е. если ООП юзать — там объекты появляются, массивы объектов — это херова туча полей и т.д.
avatar
Replikant_mih, я не программист, не знаю, у меня циферку, ну может кучу циферек, но всё равно эти циферки же что то значат — значит можно подписать)
avatar
Igr, ну просто если дебажить в Visual Studio — разворачиваешь некоторые объекты — там миллиарды полей, и некоторые поля — тоже объекты, которые тоже замороченные — короче все сложно)). Я тоже не программист.
avatar

Replikant_mih, ну в чужой программе вообще тяжело разобраться (кстати коменты очень бы помогли), а если она написана коряво — то вообще нереально 

думаю многие согласятся что написать свою легче чем разобраться в чужой) 

avatar

Igr, Комменты пишу, помогает «возвращаться» в код после перерыва. 

В своем конечно легче копаться — ну хотя если чужой написан профессионально и стройно, а свой коряво — то может проще будет в чужом))

avatar

Replikant_mih, ага, я только ради возвращения и пишу)

если к каждой строчке будет комент — то вполне возможно 

avatar
Igr, комментарии в каждой строчке первейший признак ужасного кода https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%B4_%D1%81_%D0%B7%D0%B0%D0%BF%D0%B0%D1%88%D0%BA%D0%BE%D0%BC
avatar
Михаил, возможно, но с комментариями куда как легче разобраться в чужом коде, или через продолжительное время вспомнить свой )
avatar
Igr, код должен говорить сам за себя без всяких коментов — когда код разбит на маленькие функции с говорящими именами, переменные имеют говорящие имена ( а не просто i, j, x, y) и вместо мало понятных чисел используются константы с говорящими именами комментарии практически не нужны. 
avatar

Михаил, согласен, я далеко не профи

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

avatar
Igr, проблема с комментариями, что при изменении кода вы должны менять комментарии, что часто забывают делать.

Все зависит от языка, где есть функции первого класса — переприсвойте плохое имя сторонней функции более вразумительному. Если такого синтекса в языке нет, то просто оберните вызов сторонней функции вызовом функции с понятным для вас именем. Или поставьте нормальную среду разработки, чтобы в один клик смотреть хлеп по сторонним функциям.
avatar

Михаил, пока не забывал

 

функция срабатывает, например, при изменении в стакане, её не переименовать, вот к ней комент и пишу 

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

avatar
я по частям проверяю код, затем намного легче делать и проверять что то целое
avatar
Чужой, тоже хочу так попробовать — т.е. сразу проверять, а не разматывать когда что-то по факту не работает. Хотя внутренний лентяй, конечно, будет сопротивляться))
avatar
Пишу тесты. Сколько-нибудь большой программе без тестов невозможно. 
avatar
Михаил, два человека написали про тесты — похоже, полезная вещь))
avatar
Replikant_mih, прописная азбука программирования — код должен состоять из функций максимум в несколько строк, каждая функция должна быть покрыта тестами. 
avatar
Михаил, Ясн, спасибо, почитаю — я наверно, просто не знаю толком, что это такое — поэтому не осознаю важность и полезность)
avatar
Replikant_mih, есть несколько классических книг про подходы к программировани:
Code complete 
Clean code
Refactoring: Improving the Design of Existing Code



avatar
Михаил, Спасибо!
avatar
По-моему ты спрашивал уже что-то подобное.
Я стараюсь проверять по частям. 
Хотя конечно часто бывает лень или нет сил. Но лучше проверять по частям. И потом всё вместе. 
Потому что если жопа может стрястись, она всегда стрясается.

В последнее время ошибки нахожу по принципу — «что-то тут такое странное какое-то, то так то сяк», а не «тут же блин вообще всё должно быть не так, почему оно так?»

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

ошибки будут всё равно.
avatar

ПBМ, да, тема видимо, меня цепляет — поэтому не первый пост)).

Многие баги у меня чисто на лени, с массивом, допустим, работаю — написал код, думаю: а здесь надо > или >= чтоб все элементы пройти и за границы не выйти?? — аа, ладно, так сойдет, эксепшен выпадет — починю)), а похорошему надо бы досконально все такие моменты сразу прорабатывать.

Ну и да, на бумажке это штука полезная!

avatar

Replikant_mih, сойдёт?!?   не, это не наш метод) 

потом проблем может быть куда как больше, в разы из-за этой мелочи вроде как

avatar
Igr, внутенне я понимаю, что это не лучший подход))
avatar
Replikant_mih, да, примерно так «так сойдёт» — часто бывает. и через пару лет ошибки такие отлавливаются :)
avatar
Разбивал код на блоки, это если речь идёт о торговой системе. Блоки такие — объявление переменных, вызов функции, индюки или события, условия входа, условия выхода, манименеджмент. Блоки стандартизированны и при добавлении нового участка кода дописываю функцию останова с индикацией. Если брал чужой индюк тестировал его логику с выводом сигнала на график. Каждая строка кода с комментариями описывающим логику. Эти простые правила очень облегчают жизнь, и сильно сокращают количество непонятных багов))
avatar
KNK, Да, здравые мысли, в работе по стандартным схемам — например, когда пишешь стратегии однотипные — это скорее всего действительно, помогает. Но если писать что-то новое — вообще новый тип стратегии или вообще не стратегию, а софт — там уже сложнее, не все из этих правил применимы, ну или надо как-то адаптировать.
avatar

Replikant_mih, да, однотипность, единый принцип написания, названия, всех функций хорошее дело, облегчает дальнейшую работу 

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

avatar
Сейчас…
avatar
Unworldly, Анонсирующий коммент?)))
avatar
Replikant_mih, нет, иногда отправка поста происходит случайно до его написания. ;-)
avatar
Использую, если это можно так назвать,  «croud debuging». Когда мне пользователь пишет — «Жень, у тебя тут ошибка» — благодарю его за это и правлю баг.
Евгений Шибаев, не, ну если есть пользователи или тестировщики — да, можно такое тоже использовать)
avatar
ИМХО, подход не совсем корректный. Писать простыню — это мы проходили давно. ) Сейчас я делаю так:

1. Делю задачи, которые код должен выполнить, на функции. Если сильно надо, то пишу класс или библиотеку (редкое явление).
2. Запускаю и дебажу отдельные функции. 
3. Когда они работают нормально, как ожидается, то уже складываю их в последовательность.

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

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

Баланс и решение — ДРАКОН (гуглите, мощная тема). Сначала строю блок-схему кода, а потом его пишу. Благодаря процедурной логике ДРАКОНа сложно ошибиться в реализации. А уж если ошибки всплывают, то не нужно рефакторить весь код. Всплывают баги конкретные в конкретных местах. Таким образом экономится время, и разработка становится делом техники, а не творчества.

Успехов! )
avatar

facevalue, Интересные идеи, спасибо, обдумаю.

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

>> «разработка становится делом техники, а не творчества»

да, наверное, хотелось бы к такому прийти)

avatar
ну типа каждый блок, каждый кусок кода отдельно и сразу при написании, или не сразу но отдельно, если не сразу, то когда?


Каждую функцию отдельно и сразу при написании. Пока мысли по этой функции сидят в голове в «оперативной памяти», а не от'swap'ились на диск.

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

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

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

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

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

Лениться нельзя. Можно, разве, что, в качестве баловства. В программировании «лень» не прощается. Это как раз один из моментов, когда программирование положительно влияет на индивидуума.

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

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

avatar

Unworldly, Хороший комментарий не грех и анонсировать)). Мне нравится ваш подход — основательный — про ветвления, предельные значения и аналогичное — запомню. 

Ну и идеи про то, что такой подход может привить правильную дисциплину — как трейдинг меня поменял — знаю, а сейчас осознал, что и программирование может).

avatar
Replikant_mih, да, ещё про разбивку на функции, хотя эта мысль уже неявно звучала, но не совсем чётко.

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

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

Ну, и, опять же, функция каждого уровня должна решать только одну задачу, это универсальное правило, от уровня не зависит.
avatar
Unworldly, Мне с этим дроблением на функции что не нравилось — и я, собственно, пока не вижу, как это увязать техничней. ООП. В вашей иерархии высокоуровневые функции скорее всего будут весьма ООПшны и как методы класса отлично вписываться, но чем низкоуровневей, тем они дальше будут отходить от сути класса, ближе к народу так сказать)), но при этом засовывать их нужно все равно в класс. Ну, наверно, можно схемой наименования как-то разграничивать эти уровни, и соответственно интерпретировать эти функции, типа низкоуровневые нужны только для среднеуровневых ну и т.д., соответственно когда смотришь именно на возможности класса — обращаешь внимание только на высокоуровневые — как-то так?)
avatar

Replikant_mih, в целом, да.

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

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

Тут, скорее, модель «иерархии библиотек» в качестве упрощённого ООП подойдёт лучше. Каждая «библиотека» предоставляет набор функций (сервисов), или API. «Библиотека», которая находится над ней, пользуется этим набором сервисов, чтобы предоставить уже свой набор функций более высокого уровня и так далее. «Библиотека» может использовать не одну, а несколько «библиотек» уровнем ниже. Получается такой упрощённый ООП, это значительно проще с точки зрения обычного мышления.

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

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

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

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

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

Разбивка получается сверху вниз, а реализация и отладка — в обратном порядке.

avatar

Unworldly, спасибо за подробный ответ! :) И предыдущие тоже).

C# — первый и единственный язык, который более или менее серьезно изучаю, поэтому хочется верить, что ООП я впитываю нативно). Namespae'ы их иерархия и в целом использование для организации это да, но все-таки мне важнее классы — я их активно использую, т.е. мне интересно как в классе все инкапсулируется и как в классе организовать иерархию. 

По моим умозрительным выкладкам, правильная иерархия — делать часть полей типами классов (или как там правильней по говорить)) ) — т.е. если в классе 20 полей и ты понимаешь, что 5 из них описывают какую-то дополнительную сущность — вычленять в отдельный класс и будет 15 полей и 1 поле — объект класса. Соответственно, наверно, и методы этого объекта из 5-ти полей можно в нем инкапсулировать.

Допустим мы эти 20 полей разобьем на 3 класса и 5 полей простых типов данных — это уже мы внутренние простые логики этих 3-х классов засунем в эти классы, соответственно в самом объекте можно оставить только логики (функции) взаимодействия между этими объектами-полями — уже поинтересней, вроде). А вот классы (количество которых при таком подходе будет большим) уже можно иерархично раскидывать по нэймспэйсам.

Что-то интересное выкристализовывается).

avatar
Replikant_mih, в целом, верно, только вычленять лучше на этапе проектирования, а не по факту упирания в необходимость. Хотя, для тренировки, полезно и поупираться, чтобы начать чувствовать, когда, во что и что вычленять.

Классы по namespace'ам вряд ли придётся распихивать, достаточно самих классов.

Но с классами всё сложнее. Там не только агрегирование возможно, но и наследование. Опять же, доступ к полям/методам (private, protected, public), конструкция/деструкция, а раз — классы, то обязательно — исключения… Код должен быть безопасен с точки зрения возникновения исключений… И так далее. Стоит ли оно того, чтобы всем этим и не только этим овладевать для решения своих задач?

C# не знаю вообще, принципиально не доверяю Microsoft. И в C# могут быть свои дополнительные приколы.
avatar
Unworldly, В C# столько всего наворочено — столько тем, деталей, нюансов, пока выучишь одно — забываешь другое)) — хотя наверно так во многих языках. Так что только практика — ненужные знания сами отсеятся. Хотя слишком легкомысленно отсеивать нельзя — ибо иногда приходится юзать сторонний код — а вот они могут отсеивать по другим принципам)).
avatar
Replikant_mih, так, а цель-то в чём? Я думал, сделать что-то надо, а не программистом стать. Ресурс, вроде, трейдерский, не программистский...

Программирование — огромно, оно уже переплюнуло медицину по объёму и сложности.

Так что только практика — ненужные знания сами отсеятся.

В программировании нужно понимание.
avatar
Unworldly, цель изучения языка — научиться кодить в объеме потребностей для целей алгоритмической торговли)), а это не только стратегию закодить, но и софтинку какую написать — не особо сложную, наверно, по меркам мира разработчиков, но тем не менее.
avatar
Replikant_mih, что ж, тоже способ выяснить для себя, что всё не так, как кажется. Кому-то требуется обязательно на своём опыте убедиться.
avatar
Unworldly, Снимаю шляпу. На весьма дельный вопрос — самый что ни на есть предельно профессиональный ответ.
avatar
Методом пристального взгляда

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

)))

avatar
Replikant_mih, а других методов-то и нет, если понимаешь что делаешь ) а если не понимаешь то и тест Напида-Раса не поможет )
Дублирую функции так, чтобы одно и то же вычислялось разными способами. Как пример — выборку делаю бинарным поиском и прямым. Потом сравниваю логи, полученные в двух вариантах исполнения. Если они одинаковые, значит ошибки скорее всего нет)
avatar
tranquility, эммм, нууу, даже не знаю)). Ну тоже имеет право на существование)), но не для ленивых, похоже).
avatar
Replikant_mih, уж не знаю кто более ленивый тут;) Мне, например, взападло тесткейсы придумывать. Просто на своих обычных данных прогнать и посмотреть потом «упало» что-то или нет — как-то быстрее получается в итоге, мне кажется. Алготрейдинг сам по себе не для ленивых, если дело касается разработки. А так, можно еще посоветовать объектно-ориентированный подход и вообще везде писать повторно используемый код. Так больше вероятность, что затесавшаяся где-то ошибка будет обнаружена еще до того, как система будет отправлена в бой, на этапе тестирования.
avatar

tranquility, Да, стараюсь ООП задействовать — пока, правда, затрудняюсь оценить, насколько это у меня получается)).

Да, алготрейдинг не для ленивых), мои предыдущие виды деятельности меня этому не готовили))).

avatar

в Квике на ЛУА пошагового исполнения капец как не хватает ((
а может я какого инструмента не знаю, чтоб пошагово можно было дебажить… У кого что есть на этот счет ?

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

Андрейка, я даже в Visual Studio использовал обычный вывод инфы из программы чтобы посмотреть значения переменных, только сейчас начал познавать все преимущества, красоту и элегантность дебагинговых инструментов студии.
avatar
Андрейка, Переходите на С#. Чем раньше это сделаете, тем раньше обретете спокойствие. Сам все это проходил, так что знаю о чем говорю. После того, как количество одновременно работающих роботов (с визуальными интерфейсами) превысило 10, тупиковость дальнейшей разработки на QLua стала очевидной.
avatar
серьёзно программировал год назад
вставляя в код точки пишущие в файл

и в файле читал логику на понятном языке
буквально: «если больше тогда перешли в цикл»
типа того

запись в файл реализуема хоть на qbasic
и вообще щаз вспоминаю:
у меня 99% программ пишут в файл
экспорт, Писать в файл — речь, как я понимаю, о логах?) Да, это штука полезная.
avatar
Если это код для реальной торговли, то полное дублирование алгоритма в Экселе
avatar
wrmngr, Какой-то экзотический подход)), ну и в эксель после превышения некоторой планки по сложности сложно будет алгоритм запихивать).
avatar
Replikant_mih, зато надежный. Если алгоритм не запихивается в эксель, то в 99% случаев это не стОит вообще торговать
avatar
wrmngr, типа сложные алгоритмы не работают в трейдинге — вы про это?
avatar
Replikant_mih, и это тоже
avatar
wrmngr, Мне это не подходит, мое алго-мировоззрение не опирается на этот постулат).
avatar
 В целом, вопрос достаточно интересный и объемный. Я не являюсь профессиональным программистом, но мой опыт разработки торговых роботов показал, что сильно сложная и разветвленная программная среда создается далеко не сразу, а собирается из отдельных частей. Т.е. сначала мы пишем простейшего робота, и отлаживаем его работу. Потом начинаем сталкиваться с ситуациями в которых требуется усложнение логики или добавление каких-нибудь страхующих/защитных функций. Т.к. мы уже отладили общую систему, то нам остается отладить только тот кусок кода, который мы добавляем в робота. В зависимости от того, какую конкретно задачу мы пытаемся решить, пользуемся или логированием, или пошаговой отладкой, или просто онлайн-тестированием, с наблюдением (встроенные механизмы тестирования я не использую). Через какое-то время, нам приходится создавать новых роботов на основе ранее созданных, и после того как их наберется какое-то количество, мы понимаем, что есть масса кусков кода, которые перетекают из робота в робота без изменений. Такие куски выводим в отдельные функции, быстро проверяем, что они не сломались и выводим их в отдельный класс или библиотеку, после чего к отладке этих кусков уже никогда не возвращаемся.
Таким образом, каждая новая разработка сводится к дебагингу только новых кусков кода, которые составляют лишь несколько процентов всей программы. А как именно отлаживать эти маленькие кусочки — каждый выбирает сам. Главное чтобы самому удобно было.
avatar
Prophetic, Ну да, всё это звучит логично :)
avatar

Научитесь декомпозировать, но при этом не усложнять.

Изучите базовые строительные блоки(паттерны).

Для начала достаточно создавать отдельные классы для разных бизнес сущностей.

По возможности тестируйте блоки по отдельности.

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

Тарас Громницкий, Спасибо. Вроде теперь с вектором и роудмапом определился, теперь на практике буду это все опробовать и оттачивать :).
avatar

теги блога Replikant_mih

....все тэги



UPDONW
Новый дизайн