Чистое программирование при написании научного кода


169

Я действительно не пишу большие проекты. Я не поддерживаю огромную базу данных и не имею дело с миллионами строк кода.

Мой код - это, в основном, "скриптовые" вещи - вещи для проверки математических функций или для моделирования чего-либо - "научное программирование". Самые длинные программы, над которыми я работал до сих пор, - это пара сотен строк кода, а большинство программ, над которыми я работаю, - около 150.

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

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

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

Нужно ли модульное тестирование для написания небольших кусков кода? Как насчет ООП? Какие подходы хороши для быстрого написания хорошего, чистого кода при «научном программировании», а не над большими проектами?

Я задаю эти вопросы, потому что часто само программирование не очень сложно. Это больше о математике или науке, которые я тестирую или исследую с помощью программирования. Например, нужен ли класс, когда две переменные и функция могут позаботиться об этом? (Рассмотрим также, как правило, ситуации, когда скорость программы предпочтительнее, чем на более быстром конце - когда вы выполняете более 25 000 000 временных шагов моделирования, вы как бы этого хотите.)

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

Я был бы рад предоставить более конкретные детали, но чем более общий совет, тем более полезным, я думаю. Я программирую на Python 3.


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

Есть исключения. Кроме того, есть, вероятно, стандарты для научного программирования, помимо простых стандартов. Я спрашиваю и о них. Дело не в том, что нормальные стандарты кодирования должны игнорироваться при написании научного кода, а в написании чистого научного кода!


Обновить

Просто подумал, что добавлю обновление "не совсем на неделю позже". Все ваши советы были чрезвычайно полезны. Сейчас я использую контроль версий - git, с git kraken для графического интерфейса. Он очень прост в использовании и значительно очистил мои файлы - больше нет необходимости в старых файлах или в старых версиях кода, закомментированных «на всякий случай».

Я также установил Pylint и запустил его на весь мой код. Один файл изначально получил отрицательную оценку; Я даже не уверен, как это было возможно. Мой основной файл начинался с отметки ~ 1.83 / 10, а сейчас - ~ 9.1 / 10. Весь код теперь довольно хорошо соответствует стандартам. Я также пробежался по нему своими собственными глазами, обновляя имена переменных, которые стали ... хм ... ошибочными, и искал разделы для рефакторинга.

В частности, я недавно задал вопрос на этом сайте о рефакторинге одной из моих основных функций, и теперь она намного чище и намного короче: вместо длинной раздутой функции, заполненной если / еще, теперь она меньше половины Размер и гораздо проще понять, что происходит.

Мой следующий шаг - реализация «модульного тестирования». Под этим я подразумеваю файл, который я могу запустить в своем главном файле, который просматривает все функции в нем с помощью операторов assert и try / excepts, что, вероятно, не лучший способ сделать это, и приводит к большому количеству дублирующегося кода, но я буду продолжать читать и попытаюсь выяснить, как сделать это лучше.

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

Итак ... Я думаю, это все, чтобы сказать: спасибо .




8
Если вы активно хотите улучшить свой код, вы можете разместить его на Code Review . Сообщество там с радостью поможет вам в этом.
hoffmale

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

2
@mathreadler Я не думаю, что вы понимаете. Да, только я, вероятно, каждый собираюсь на самом деле читать и связываться с кодом (хотя вы никогда не знаете, и у меня есть кто-то, с кем я работаю, который может программировать, хотя и на другом языке) ... но код все еще дерьмо. Я должен буду прочитать это позже и выяснить снова, какого черта я делаю. Это является проблемой, и я могу засвидетельствовать это , потому что я испытываю влияние сейчас, и все стало проще , так как я реализовал систему управления версиями и другие методы , предложенные здесь.
вереск

Ответы:


163

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

Так что ваши сценарии - беспорядок. Я собираюсь пойти против здравого смысла и сказать, что, если вы программируете в одиночку, это не так уж плохо! Вы никогда больше не будете касаться большей части того, что пишете, поэтому тратить слишком много времени на написание красивого кода вместо того, чтобы создавать «значение» (то есть результат вашего скрипта), мало что вам поможет.

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

Ваша главная проблема - удобочитаемость, поэтому вот несколько советов по улучшению:

Имена переменных:

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

Разделите ваш код на функции:

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

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

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

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

Глобальные переменные:

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

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

Контроль версий

Это напрямую не улучшает читаемость, но помогает вам делать все вышеперечисленное. Всякий раз, когда вы вносите какие-либо изменения, фиксируйте управление версиями (локальный репозиторий Git будет достаточно хорош), а если что-то не работает, посмотрите, что вы изменили, или просто откатитесь! Это облегчит рефакторинг вашего кода и станет защитной сетью, если вы случайно сломаете что-то.

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


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

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

11
@cHao проблема не в том, когда эти переменные включены в формулу (отсюда совет "переименовать их внутри функции"), а в том, что они читаются и управляются вне ее, и когда они начинают конфликтовать с другими переменными в конце строки (например, я видел людей, которым нужны три переменные «x», которые называют их x1, x2, x3)
BgrWorker

4
"Я пойду против здравого смысла ..." Нет, ты не. Вы идете против господствующей догмы, которая сама против здравого смысла. ;) Это все отлично звучащий совет.
jpmc26

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

141

Физик здесь. Был там.

Я бы сказал, что ваша проблема не в выборе инструментов или парадигм программирования (модульное тестирование, ООП и т. Д.). Это касается отношения , мышления. Тот факт, что имена ваших переменных вначале были правильно выбраны и в конечном итоге оказались дерьмовыми, достаточно показателен. Если вы думаете о своем коде как «запустить один раз, а затем выбросить», то это неизбежно приведет к путанице. Если вы думаете о нем как о продукте ремесла и любви, это будет красиво.

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

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

Написание чистого кода точно так же. Думайте о своем коде как о подробном объяснении алгоритма, который только случайно оказывается машиночитаемым. Представьте, что вы собираетесь опубликовать это как статью, которую люди будут читать. Вы даже собираетесь показать это на конференции и пройтись по аудитории построчно. Теперь репетируйте свою презентацию . Да, строка за строкой ! Смущает, не так ли? Так что почистите свои слайды (ошибаюсь ... я имею в виду, ваш код) и повторите репетицию. Повторяйте, пока не будете удовлетворены результатом.

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

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

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


32
«Написание чистого кода - это то же самое, что и написание чистой статьи». Я полностью поддерживаю это, хорошо говоря!
juandesant

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

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

3
@ UEFI Нет, это то, что большинство профессиональных программистов даже не осознают. Или не волнует.
jpmc26

2
Согласитесь на 100%. Статистик стал программистом, поэтому я занимаюсь «научным» программированием на работе. Ясный код с содержательными комментариями спасет вас, когда вам придется вернуться к этому коду через 1, 4 или 12 месяцев. Чтение кода говорит вам, что делает код. Чтение комментариев покажет вам, что должен делать код.
railsdog

82

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

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

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

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


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

22
Уловка @KonradRudolph в этих случаях, вероятно, заключается в чистом разделении проблем между частями вашего кода, которые имеют четко определенное поведение (прочитайте этот ввод, вычислите это значение), с частями вашего кода, которые либо являются подлинно исследовательскими, либо адаптируются к, например, некоторый читабельный вывод или визуализация. Проблема здесь, вероятно, заключается в том, что плохое разделение интересов приводит к размытию этих линий, что приводит к восприятию того, что модульное тестирование в этом контексте невозможно, что возвращает вас к началу повторяющегося цикла.
Муравей Р

18
Напомним, что управление версиями также очень хорошо работает для документов LaTeX, поскольку формат допускает диффузию текста. Таким образом, у вас может быть хранилище как для ваших статей, так и для кода, который их поддерживает. Я предлагаю изучить распределенный контроль версий, например, Git. Есть некоторая кривая обучения, но как только вы ее поймете, у вас появится хороший и понятный способ итерировать свою разработку, и у вас есть несколько интересных вариантов использования платформы, такой как Github, которая предлагает бесплатные групповые учетные записи для академиков .
Дэн Брайант

12
@AntP Вполне возможно, что не так уж много кода, который может быть значительно переработан в четко определенные тестируемые модули. Большая часть научного кода, по сути, объединяет множество библиотек. Эти библиотеки уже будут хорошо протестированы и четко структурированы, а это означает, что автору нужно только написать «клей», и, по моему опыту, практически невозможно написать модульные тесты для клея, которые не являются тавтологическими.
James_pic

7
«Между управлением версиями и написанием модульных тестов по ходу работы ваш код, естественно, станет намного чище». Это не правда Я могу засвидетельствовать это лично. Ни один из этих инструментов не останавливает вас от написания дрянного кода, и, в частности, написание дрянных тестов поверх дрянного кода только усложняет его очистку. Тесты - не волшебная серебряная пуля, и говорить так, как они, - ужасная вещь для любого разработчика, который все еще учится (а это все). Контроль версий, как правило, никогда не наносит ущерба самому коду, как это делает плохое тестирование.
jpmc26

29

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

Вы, наверное, сами бы это поняли, но если вам нужно « записать все это в текстовый файл где-то », вы не используете систему контроля версий в полной мере. Используйте что-то вроде Subversion, git или Mercurial и пишите хорошее сообщение о коммите с каждым коммитом, и у вас будет журнал, который служит цели текстового файла, но не может быть отделен от хранилища.

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

Нужно ли модульное тестирование для написания небольших кусков кода? Как насчет ООП? Какие подходы хороши для быстрого написания хорошего, чистого кода при «научном программировании», а не над большими проектами?

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

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


4
+1 для фиксации сообщений; они похожи на комментарии, которые не могут устареть, потому что они привязаны к версии кода, когда они действительно применимы. Чтобы понять ваш старый код, проще просмотреть историю проекта (если изменения приняты с разумной степенью детализации), чем читать устаревшие комментарии.
Глупый урод

Subversion, git и Mercurial не являются взаимозаменяемыми. Я бы настоятельно рекомендовал использовать Git (или Mercurial) с локальным хранилищем для Subversion. С сольным кодером недостатки Subversion менее
важны,

2
@mcottle, я лично предпочитаю git, но я не думаю, что это было правильное место, чтобы вдаваться в подробности о различиях, тем более что выбор - одна из активных религиозных войн. Лучше побудить ОП использовать что-то, чем отпугивать их от области, и решение в любом случае не является постоянным.
Питер Тейлор

21

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

Если вы пишете сценарии для выполнения научных расчетов, у вас, вероятно, есть документы с уравнениями или алгоритмами, которые вы используете, написанные в них. Если вы используете новые идеи, которые вы открыли самостоятельно, вы надеетесь опубликовать их в своих собственных статьях. В этом случае правило таково: вы хотите, чтобы ваш код читался как можно больше опубликованных уравнений. Вот ответ на Software Engineering.SE с более чем 200 ответами, поддерживающими этот подход и объясняющими, как он выглядит: Есть ли оправдание коротким именам переменных?

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

ContactGeometry.cpp:

// t = (-b +/- sqrt(b^2-4ac)) / 2a
// Discriminant must be nonnegative for real surfaces
// but could be slightly negative due to numerical noise.
Real sqrtd = std::sqrt(std::max(B*B - 4*A*C, Real(0)));
Vec2 t = Vec2(sqrtd - B, -sqrtd - B) / (2*A);

ContactGeometry_Sphere.cpp:

// Solve the scalar Jacobi equation
//
//        j''(s) + K(s)*j(s) = 0 ,                                     (1)
//
// where K is the Gaussian curvature and (.)' := d(.)/ds denotes differentiation
// with respect to the arc length s. Then, j is the directional sensitivity and
// we obtain the corresponding variational vector field by multiplying b*j. For
// a sphere, K = R^(-2) and the solution of equation (1) becomes
//
//        j  = R * sin(1/R * s)                                        (2)
//          j' =     cos(1/R * s) ,                                      (3)
//
// where equation (2) is the standard solution of a non-damped oscillator. Its
// period is 2*pi*R and its amplitude is R.

// Forward directional sensitivity from P to Q
Vec2 jPQ(R*sin(k * s), cos(k * s));
geod.addDirectionalSensitivityPtoQ(jPQ);

// Backwards directional sensitivity from Q to P
Vec2 jQP(R*sin(k * (L-s)), cos(k * (L-s)));
geod.addDirectionalSensitivityQtoP(jQP);

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

@DavidHammen: как аспирант, я уважаю это. Как программист, я бы тогда настаивал на том, чтобы в верхней части каждой функции был гигантский блок комментариев, описывающий простым языком (или языком по вашему выбору), что означала каждая переменная, даже если это просто временный заполнитель. Таким образом, у меня по крайней мере есть ссылка, к которой я хотел бы вернуться.
tonysdg

1
@DavidHammen Кроме того, поддержка Python для UTF-8 в исходных файлах и простые правила для имен переменных позволяют легко объявлять λили φвместо уродливых lambda_или phy...
Матиас Эттингер,

1
@tonysdg У вас уже есть ссылка; это называется «Хаммен и др. (2018)» (или как там). Он объяснит значения переменных гораздо более подробно, чем любой блок комментариев. Причина, по которой имена переменных должны быть близки к обозначениям в документе, заключается именно в том, чтобы упростить соединение того, что в документе, с тем, что в коде.
Никто

17

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

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

В связи с этим, если вы хотите что-то конкретное, ориентированное на исследователей, а не на разработчиков программного обеспечения, я не могу рекомендовать организацию Software Carpentry достаточно высоко. Если вы можете посетить один из их семинаров , отлично; если все, что у вас есть время / доступ, это прочитать некоторые из их статей о передовых методах научных вычислений , это тоже хорошо. Из последнего:

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

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

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

Краткое описание методов, которые они рекомендуют:

  1. Пишите программы для людей, а не компьютеров
  2. Пусть компьютер сделает работу
  3. Вносить дополнительные изменения
  4. Не повторяй себя (или других)
  5. План ошибок
  6. Оптимизируйте программное обеспечение только после того, как оно работает правильно
  7. Дизайн документа и цель, а не механика
  8. сотрудничать

В статье подробно рассматриваются все эти вопросы.


16

Действительно ли имеет смысл писать код, например, ООП, когда можно было бы выполнить какой-то стандартный материал, который был бы написан намного быстрее и был бы схожим по уровню читаемости из-за нехватки программы?

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

Общий ответ:
«Это зависит». Но если вы изо всех сил пытаетесь понять, когда использовать концепцию программирования или парадигмы, вот пара вещей, о которых стоит подумать:

  • Масштабируемость. Будет ли скрипт работать автономно или в конечном итоге будет использоваться в более крупной программе? Если да, то в более широком программировании используется ООП? Может ли код в вашем скрипте быть легко интегрирован в большую программу?
  • Модульность: в целом, ваш код должен быть модульным. Однако ООП разбивает код на куски особым образом. Имеет ли этот тип модульности (то есть разбивает ваш скрипт на классы) смысл для того, что вы делаете?

Я хочу знать, как «начать все сначала» и аккуратно программировать эти небольшие, более быстрые проекты.

# 1: Познакомьтесь с тем, что там происходит:
даже если вы «просто» пишете сценарии (и вы действительно заботитесь о научном компоненте), вам нужно некоторое время, чтобы узнать о различных концепциях и парадигмах программирования. Таким образом, вы сможете лучше понять, что вы должны / не должны использовать и когда. Это может звучать немного устрашающе. И у вас все еще может возникнуть вопрос: "С чего мне начать / на что мне начать смотреть?" Я пытаюсь объяснить хорошую отправную точку в следующих двух пунктах.

# 2: Начните исправлять то, что, как вы знаете, неправильно.
Лично я бы начал с того, что, как я знаю, неправильно. Получите некоторый контроль версий и начните дисциплинировать себя, чтобы стать лучше с этими именами переменных (это серьезная борьба). Исправление того, что вы знаете неправильно, может показаться очевидным. Однако по своему опыту я обнаружил, что исправление одной вещи приводит меня к чему-то другому и так далее. Прежде чем я это узнал, я представил 10 разных вещей, которые я делал неправильно, и выяснил, как их исправить или как правильно их реализовать.

№3. Найдите партнера по программированию.
Если «начинать заново» для вас не предполагает формальных занятий, подумайте о том, чтобы объединиться с разработчиком и попросить его проверить ваш код. Даже если они не понимают научную часть того, что вы делаете, они могут рассказать вам, что вы могли бы сделать, чтобы сделать ваш код более элегантным.

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

Надеюсь, это поможет!


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


Я думаю, что № 3 - это самая важная проблема - опытный программист может рассказать оператору о том, что им нужно (# 1), как лучше организовать сценарии и как использовать контроль версий (# 2).
Док Браун

16

Я бы порекомендовал придерживаться принципа Unix: Keep It Simple, Stupid! (ПОЦЕЛУЙ)

Или, говоря по-другому: делай одну вещь за раз и делай это хорошо.

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

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

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

Это особенно верно, когда рассматриваемая функция была запрограммирована с помощью copy-paste. Всякий раз, когда вы видите такой повторяющийся паттерн, вы сразу же знаете, что его, скорее всего, следует превратить в собственную функцию. Это принцип Не повторяйся (СУХОЙ) . Всякий раз, когда вы нажимаете «копировать-вставить», вы делаете что-то не так! Вместо этого создайте функцию.

Анекдот
Однажды я потратил несколько месяцев на рефакторинг кода, который имел функции около 500 строк в каждой. После того, как я закончил, общий код был примерно на тысячу строк короче; Я произвел отрицательный вывод с точки зрения строк кода. Я был должен компании ( http://www.geekherocomic.com/2008/10/09/programmers-salary-policy/index.html ). Тем не менее, я твердо верю, что это была одна из моих самых ценных работ, которые я когда-либо делал ...

Некоторые функции могут быть длинными, потому что они делают несколько разных вещей одну за другой. Это не СУХИЕ нарушения, но они также могут быть разделены. Результатом часто является функция высокого уровня, которая вызывает множество функций, которые реализуют отдельные шаги исходных функций. Это обычно увеличивает размер кода, но имена добавленных функций творит чудеса, делая код более читабельным. Потому что теперь у вас есть функция верхнего уровня, все шаги которой явно названы. Кроме того, после этого разделения ясно, какой шаг влияет на какие данные. (Аргументы функции. Вы не используете глобальные переменные?)

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

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

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


2
Замечательные замечания о расщеплении длинных методов. Еще одна хорошая эвристика в отношении первого абзаца после анекдота: если ваш метод можно логически разделить на разделы, и у вас возникает соблазн написать комментарий, объясняющий, что делает каждый раздел, то он должен быть разбит на части в комментариях. Хорошие новости, эти комментарии, вероятно, дают вам хорошее представление о том, как называть новые методы.
Jaquez

@Jaquez Ах, совсем забыл об этом. Спасибо за напоминание. Я обновил свой ответ, чтобы включить это :-)
Cmaster

1
Замечательно, я хотел бы упростить это, чтобы сказать, что «СУХОЙ» является наиболее важным фактором. Выявление «повторов» и их удаление является краеугольным камнем почти всех других программных конструкций. Иными словами, все программные конструкции существуют, по крайней мере, частично, чтобы помочь вам создать СУХОЙ код. Начните с того, что говорите «Дублирование никогда не будет», а затем попробуйте найти и устранить его. Будьте очень открыты в отношении того, что может быть дубликатом - даже если это не похожий код, это может дублировать функциональность ...
Билл К

11

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

  • Не нужно вести список изменений, которые были внесены, или иметь много копий файла и т. Д., Так как об этом заботится контроль версий.
  • Больше нет файлов, потерянных из-за перезаписи и т. Д. (При условии, что вы просто придерживаетесь основ;
  • Нет необходимости в устаревших комментариях, мертвом коде и т. Д., Которые хранятся «на всякий случай»; как только они перейдут на контроль версий, не стесняйтесь обстреливать их. Это может чувствовать себя очень освобождающим!

Я бы сказал, не думайте об этом: просто используйте git. Придерживайтесь простых команд (например, только одна masterветвь), возможно, используйте графический интерфейс, и у вас все будет хорошо. В качестве бонуса вы можете использовать gitlab, github и т. Д. Для бесплатной публикации и резервного копирования;)

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

  • Модульные тесты должны решить, какие данные они передадут функции. Эти данные должны быть реалистичными (иначе нет смысла проверять их), они должны иметь правильный формат и т. Д.
  • Модульные тесты должны иметь «доступ» к вещам, которые они хотят утверждать. В частности, модульные тесты не могут проверять какие-либо промежуточные данные внутри функции; мы должны были бы разбить эту функцию на более мелкие части, протестировать эти части и соединить их вместе где-нибудь еще.
  • Также предполагается, что юнит-тесты имеют отношение к программе. Например, наборы тестов могут устареть, если с момента их последнего запуска произошли большие изменения, и даже может быть куча тестов для кода, который даже больше не используется.

Утверждения не имеют этих недостатков, так как они проверяются во время обычного выполнения программы. В частности:

  • Поскольку они выполняются как часть нормального выполнения программы, у нас есть реальные данные, с которыми можно поиграть. Это не требует отдельного курирования и (по определению) является реалистичным и имеет правильный формат.
  • Утверждения могут быть записаны в любом месте кода, поэтому мы можем размещать их там, где у нас есть доступ к данным, которые мы хотим проверить. Если мы хотим проверить какое-то промежуточное значение в функции, мы можем просто поместить некоторые утверждения в середину этой функции!
  • Поскольку они написаны in-line, утверждения не могут быть «не синхронизированы» со структурой кода. Если мы удостоверимся, что утверждения проверены по умолчанию, нам также не нужно беспокоиться о том, что они станут «устаревшими», так как мы сразу увидим, пройдут ли они при следующем запуске программы!

Вы упоминаете скорость как фактор, и в этом случае проверка утверждений может быть нежелательной в этом цикле (но все же полезной для проверки настройки и последующей обработки). Однако почти все реализации утверждений предоставляют способ отключить их; например, в Python их можно отключить, запустив с -Oопцией (я не знал этого, так как никогда раньше не чувствовал необходимости отключать какие-либо из моих утверждений). Я бы порекомендовал вам оставить их напо умолчанию; если ваш цикл кодирования / отладки / тестирования замедляется, вам может быть лучше проводить тестирование с меньшим подмножеством ваших данных или выполнять меньше итераций некоторого моделирования во время тестирования или чего-либо еще. Если вы в конечном итоге отключаете утверждения в не тестовых прогонах по соображениям производительности, первое, что я рекомендую вам сделать, это определить , действительно ли они являются источником замедления! (Очень легко обмануть себя, когда дело касается узких мест в производительности)

Мой последний совет - использовать систему сборки, которая управляет вашими зависимостями. Я лично использую Nix для этого, но слышал и хорошие отзывы о Guix . Есть также альтернативы, такие как Docker, которые гораздо менее полезны с научной точки зрения, но, возможно, немного более знакомы.

Такие системы, как Nix, только недавно стали (немного) популярными, и некоторые могут счесть их излишними для «выброшенного» кода, как вы описываете, но их выгода для воспроизводимости научных вычислений огромна. Рассмотрим сценарий оболочки для запуска эксперимента, например так run.sh:

#!/usr/bin/env bash
set -e
make all
./analyse < ./dataset > output.csv

Вместо этого мы можем переписать его в «деривацию» Nix, например, так run.nix:

with import <nixpkgs> {};
runCommand "output.csv" {} ''
  cp -a ${./.} src
  cd src
  make all
  ./analyse < ./dataset > $out
''

Между ними ''...''есть код bash, такой же, как у нас раньше, за исключением того, что ${...}его можно использовать для «склеивания» содержимого других строк (в этом случае ./., которое расширится до пути к каталогу, содержащему run.nix). with import ...Линия импортирует Никс в стандартную библиотеку , которая обеспечивает runCommandдля запуска кода Баш. Мы можем запустить наш эксперимент, используя nix-build run.nix, который выдаст путь, как /nix/store/1wv437qdjg6j171gjanj5fvg5kxc828p-output.csv.

Так, что это покупает нас? Nix автоматически настроит «чистую» среду, в которой есть доступ только к тем вещам, которые мы явно просили. В частности, он не имеет доступа к таким переменным, как $HOMEили к системному программному обеспечению, которое мы установили. Это делает результат независимым от деталей нашего текущего компьютера, таких как содержимое ~/.configили версии программ, которые мы установили; АКА то, что мешает другим людям копировать наши результаты! Вот почему я добавил, чтоcpкоманда, так как проект не будет доступен по умолчанию. Может показаться раздражающим, что программное обеспечение системы недоступно для сценария Nix, но оно идет и другим путем: нам не нужно ничего устанавливать в нашей системе (кроме Nix), чтобы использовать его в сценарии; мы просто просим об этом, и Nix отключится и извлечет / скомпилирует / все, что необходимо (большинство вещей будет загружено в виде двоичных файлов; стандартная библиотека также огромна!). Например, если нам нужна группа конкретных пакетов Python и Haskell для некоторых конкретных версий этих языков, а также некоторые другие ненужные файлы (потому что почему бы и нет?):

with import <nixpkgs> {};
runCommand "output.csv"
  {
    buildInputs = [
      gcc49 libjson zlib
      haskell.packages.ghc802.pandoc
      (python34.withPackages (pyPkgs: [
        pyPkgs.beautifulsoup4 pyPkgs.numpy pyPkgs.scipy
        pyPkgs.tensorflowWithoutCuda
      ]))
    ];
  }
  ''
    cp -a ${./.} src
    cd src
    make all
    ./analyse < ./dataset > $out
  ''

То же самое nix-build run.nixзапустит это, получая сначала все, что мы просили (и кэшируя все на случай, если мы захотим позже). Вывод (любой вызванный файл / каталог $out) будет сохранен Nix, который является путем, который он выбрасывает. Он идентифицируется криптографическим хэшем всех запрошенных нами входных данных (содержимое скрипта, другие пакеты, имена, флаги компилятора и т. Д.); эти другие пакеты идентифицируются по хэшам их входных данных, и так далее, чтобы у нас была полная цепочка всего, вплоть до версии GCC, которая компилировала версию GCC, которая компилировала bash, и так далее!

Надеюсь, я показал, что это очень дорого для научного кода, и с ним довольно легко начать работу. Это также начинает восприниматься очень серьезно учеными, например (топ Google) https://dl.acm.org/citation.cfm?id=2830172, поэтому может стать ценным навыком для совершенствования (как программирование)


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

9

Не вдаваясь в полноценное управление версиями + упаковка + модульные тесты (что является хорошей практикой программирования, которой вы должны попытаться достичь в какой-то момент), одно промежуточное решение, которое, я думаю, подойдет, - это использование Jupiter Notebook . Кажется, это лучше интегрируется с научными вычислениями.

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


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

Для кого-то, кто хочет читать больше, это также известно как грамотное программирование.
ЛОП

6

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

Нужно ли модульное тестирование для написания небольших кусков кода?

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

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

Допустим, у вас есть приложение, в котором библиотека A выполняет возведение в квадрат чисел, а библиотека B применяет теорему Пифагора. Очевидно, что B зависит от A. Вам нужно что-то исправить в библиотеке A, и, скажем, вы вводите ошибку, которая кубизирует числа вместо их возведения в квадрат.

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

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

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

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

Как насчет ООП?

ООП - это способ думать об отдельных объектах, например:

Когда кто-то Customerхочет приобрести Product, он говорит, Vendorчтобы получить Order. Затем Accountantбудет платить Vendor.

Сравните это с тем, как функциональный программист думает о вещах:

Когда клиент хочет purchaseProduct(), он talktoVendor()так и будет sendOrder()ему. Бухгалтер будет тогда payVendor().

Яблоки и апельсины. Ни один из них объективно не лучше другого. Интересно отметить, что для ООП Vendorупоминается дважды, но это относится к одной и той же вещи. Впрочем, для функционального программирования talktoVendor()и payVendor()есть две разные вещи.
Это демонстрирует разницу между подходами. Если между этими двумя действиями существует много общей логики, специфичной для поставщика, то ООП поможет уменьшить дублирование кода. Однако, если между ними нет общей логики, объединение их в один Vendor- бесполезная работа (и, следовательно, функциональное программирование более эффективно).

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

Какие подходы хороши для быстрого написания хорошего, чистого кода при «научном программировании», а не над большими проектами?

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

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

Тот же аргумент возвращается сюда.

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

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

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

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

Помимо принципов ООП, основная причина, по которой я пишу классы для хранения нескольких значений данных, заключается в том, что это упрощает объявление параметров метода и возвращаемых значений. Например, если у меня много методов, которые используют местоположение (широта / долгота), я быстро устану печатать float latitude, float longitudeи предпочитаю писать Location loc.

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

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

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

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

(Рассмотрим также, как правило, ситуации, когда скорость программы предпочтительнее, чем на более быстром конце - когда вы выполняете более 25 000 000 временных шагов моделирования, вы как бы этого хотите.)

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

В этом смысле не имеет значения, используете ли вы ООП или нет. Объем памяти остается прежним.

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

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

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


6

Преимущества чистого научного кода

  • ... глядя на книги по программированию, они часто рассматриваются в более крупных проектах.

  • ... действительно ли имеет смысл писать код, например, ООП, когда можно было бы выполнить какой-то стандартный материал, который был бы написан намного быстрее и был бы схожим по уровню читаемости из-за нехватки программы?

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

  • Почему они открыли этот файл?
  • Что они ищут?

Из моего опыта,

Чистый код должен облегчить проверку ваших результатов

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

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

Чистый код может служить примером кода для будущих кодеров

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

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

рекомендации

«Цитировать» математические формулы, используя комментарии.

  • Добавляйте комментарии, чтобы цитировать математические формулы, особенно если вы использовали оптимизацию (триггерные тождества, ряды Тейлора и т. Д.).
  • Если вы получили формулу из книги, добавьте комментарий о том John Smith Method from Some Book 1st Ed. Section 1.2.3 Pg 180, что , если вы нашли формулу на веб-сайте или в газете, приведите и ее.
  • Я бы рекомендовал избегать комментариев "только ссылки", убедитесь, что вы где-то ссылаетесь на метод по имени, чтобы люди могли его погуглить. Я столкнулся с некоторыми комментариями "только ссылки", которые перенаправляются на старые внутренние страницы, и они могут быть очень неприятными. ,
  • Вы можете попытаться напечатать формулу в своем комментарии, если ее все еще легко читать в Unicode / ASCII, но это может быть очень неудобно (комментарии к коду не являются LaTeX).

Используйте комментарии с умом

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

Используйте описательные имена переменных

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

Напишите код для запуска вашей программы против известных хороших и известных плохих данных.

Нужно ли модульное тестирование для написания небольших кусков кода?

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

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

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

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

Подумайте об использовании чего-то вроде перекрестной проверки , см. Этот пост о перекрестной проверке для получения дополнительной информации.

Использовать контроль версий

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

Преимущества:

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

Будьте осторожны при копировании / вставке кода

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

  • Копирование / вставка кода может сэкономить ваше время, но это одна из самых опасных вещей, которые вы можете сделать, особенно если это код, который вы не написали самостоятельно (например, если это код от коллеги).

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



6

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

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

Что тогда принесет вам пользу?

  • Контроль версий хорош тем, что позволяет легко создавать резервные копии вашей работы. Начиная с 2018 года, Github является очень популярным местом для этого (и вы всегда можете переместить его позже, если необходимо - Git очень гибок). Дешевой и простой заменой резервных копий являются автоматические процедуры резервного копирования в вашей операционной системе (Time Machine для Mac, rsync для Linux и т. Д.). Ваш код должен быть в нескольких местах!
  • Модульные тесты хороши тем, что если вы пишете их в первую очередь, вы должны подумать о том, как проверить, что на самом деле делает код, что поможет вам разработать более полезный API для вашего кода. Это полезно, если вы когда-нибудь начинаете писать код для повторного использования, и помогает изменить алгоритм, потому что вы знаете, что он работает в этих случаях.
  • Документация. Научитесь писать правильную документацию на используемом вами языке программирования (например, javadoc для Java). Пиши на будущее тебе. В этом процессе вы обнаружите, что хорошие имена переменных облегчают документирование. Итерация. Уделите столько же внимания своей документации, сколько поэт - стихам.
  • Используйте хорошие инструменты. Найдите IDE, которая поможет вам, и хорошо ее изучите. Рефакторинг, как переименование переменных в лучшее имя, намного проще.
  • Если у вас есть коллеги, рассмотрите возможность использования рецензирования. Если посторонний человек увидит и поймет ваш код, это версия будущего, для которой вы пишете. Если ваш коллега не понимает ваш код, вы, вероятно, не поймете позже.

Как этот ответ не получил upvote? Это сейчас. Наша группа считает, что рецензирование является одним из наиболее эффективных инструментов, гораздо более важным, чем модульные тесты, когда дело доходит до научного кода. При переводе сложного набора уравнений в статье из научного журнала в код легко ошибиться. Ученые и инженеры часто делают для крайне бедных программистов; рецензирование может выявить недостатки архитектуры, которые затрудняют поддержку / понимание / использование кода.
Дэвид Хаммен

5

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

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

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

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

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


5

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

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

Если вы хотите , чтобы остановить себя от написания грязного кода, вам нужен инструмент , который работает , где мур происходит: когда вы писать код. Популярный инструмент, который делает это, называется линтером. Я не разработчик Python, но похоже, что Pylint может быть хорошим вариантом.

Линтер смотрит на код, который вы написали, и сравнивает его с настраиваемым набором лучших практик. Если у линтера есть правило, что переменные должны быть camelCase, и вы записываете их snake_case, это помечается как ошибка. У хороших линтеров есть правила, варьирующиеся от «должны использоваться объявленные переменные» до «Цикломатическая сложность функций должна быть меньше 3».

Большинство редакторов кода могут быть настроены для запуска linter каждый раз, когда вы сохраняете, или просто в целом, когда вы печатаете, и указывают на проблемы в строке. Если вы наберете что-то вроде x = 7, xбудет выделено, с инструкцией, чтобы использовать более длинное, лучшее имя (если это то, что вы настроили). Это работает как проверка орфографии в большинстве текстовых процессоров, затрудняя игнорирование и помогая выработать лучшие привычки.


Это должно иметь гораздо больше голосов. +1
вереск

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

4

Все, что вы перечислили, является метафорическим инструментом. Как и все в жизни, разные инструменты подходят для разных задач.

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

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

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

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


4

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

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

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


4

Какие качества важны для такой программы?

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

Вероятно, не имеет значения, насколько это эффективно.

Вероятно, не имеет значения, имеет ли он отличный пользовательский интерфейс или защищен от злоумышленников.

Может иметь значение, что он читабелен: что кто-то, читающий ваш код, может легко убедить себя, что он делает то, что заявляет.

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

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

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


4

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

Например, вам, возможно, придется иметь дело с множеством преобразований координат в вашей области (ECEF, NED, lat / lon, WGS84 и т. Д.), Поэтому такая функция convert_ecef_to_ned()должна войти в новый проект с именем CoordinateTransformations. Поместите проект под контроль версий и разместите его на серверах вашего отдела, чтобы другие люди могли его использовать (и, надеюсь, улучшить). Затем через несколько лет у вас должна появиться надежная коллекция библиотек, в которых ваши проекты содержат только код, специфичный для конкретной проблемы / области исследования.

Еще несколько общих советов:

  • Всегда стремитесь смоделировать вашу конкретную проблему как можно точнее, независимо от того, что это такое. Таким образом, вопросы разработки программного обеспечения, такие как что / где / как поместить переменную, должны стать более очевидными.
  • Я не стал бы беспокоиться о разработке, основанной на тестировании, поскольку научный код описывает идеи и концепции и является более креативным и плавным; нет никаких API для определения, сервисов для обслуживания, рисков для кода других людей при изменении функциональности и т. д.

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

@mathreadler Ну, если это обычные служебные библиотеки, то другим будет немного сложно все испортить, вот в чем идея.
jigglypuff

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

@mathreadler Потому что, как правило, есть только один способ, например, для преобразования координат или преобразования единиц.
jigglypuff

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

3

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

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

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

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

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

Для моих собственных одноразовых сценариев (часто для таких вещей, как проверка оценки вероятности и т. П.) Я обнаружил две очень полезные вещи: 1. Включите комментарий, показывающий, как используется этот код. 2. Включите краткое описание того, почему вы написали код. Эти вещи ужасно очевидны, когда вы пишете код, но очевидность теряется со временем :-).

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


3

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

Но мне действительно нравятся интеграционные тесты для научного кода:

Выделите небольшой фрагмент своего кода, который может работать самостоятельно, например, конвейер ETL. Затем напишите тест, который предоставляет данные, запустите конвейер etl (или просто шаг), а затем проверьте, что результат соответствует вашим ожиданиям. Несмотря на то, что в тестируемом фрагменте может быть много кода, тест дает еще одно значение:

  1. У вас есть удобный хук для повторного выполнения кода, который помогает часто его запускать.
  2. Вы можете проверить некоторые предположения в своем тесте
  3. Если что-то ломается, легко добавить провальный тест и исправить
  4. Вы кодифицируете ожидаемые входы / выходы, избегая обычной головной боли, которая возникает в результате попытки угадать формат входных данных.
  5. Несмотря на то, что IT-тесты не так скудны, как модульные тесты, они все же помогают разбить ваш код на части и вынудить вас добавить некоторые границы в ваш код.

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


2

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

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


2

С написанием кода - как и с написанием в целом - главный вопрос:

Какой читатель ты имеешь в виду? или кто потребляет твой код?

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

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

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

Я думаю, что вам не нужны ООП или юнит-тесты для файлов 150 LOC. Выделенный VCS будет интересен, когда у вас есть развивающийся код. В противном случае это .bakделает трюк. Эти инструменты являются лекарством от болезни, вы можете даже не иметь.

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.