Ищем ошибки в C, C++ и C# на Windows и Linux
460,18
рейтинг
5 сентября 2013 в 11:23

Разработка → Большой Калькулятор выходит из под контроля

Calculator

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


Большой калькулятор


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

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

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

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

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

Имею кое-что сказать по этой теме исходя из личного опыта. Будучи профессиональным программистом, по образованию я — потомственный физик. Так уж вышло, что в тот момент, когда я выбирал ВУЗ, голос крови оказался сильнее, чем вера в светлое будущее IT. И я поступил в достаточно престижную по местным меркам физическую высшую школу, которая, по сути, является «детским садиком» при крупном НИИ в родном городе Нижнем Новгороде. Люди, знающие тему, узнают и НИИ, и название школы.

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

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

2. Как следствие из этого факта, программист — это не профессия. Программист — это просто тот человек, который умеет пользоваться Большим Калькулятором, чтобы построить означенный график. Каким способом график будет построен, не имеет значения. Совсем. Как-как вы сказали? Статический анализ? Контроль версий? Окститесь, родные! C++ — язык для программистов. Физики пишут на Фортране!

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

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

И тем, кто не заскучал, второй пример. Мой отец, находясь в пенсионном возрасте, тем не менее работает в очень крупном оборонно-инженерном предприятии, здесь же, в Нижнем (самом крупном в городе и одном из крупнейших в стране — те, кто в теме опять же угадают ;) ). Он всю жизнь программировал на Фортране. Начинал еще с перфокарт. Я не виню его за то, что не учит C++. Ему это уже 10 лет назад было поздно — он еще неплохо держится. Но на предприятии, где 2/3 сотрудников что-то как-то программируют, приняты следующие меры безопасности:

1. Никакого интернета. Совсем. Нужна литература — иди в местную библиотеку. Stack Overflow? А что это? Даже если ты хочешь отправить письмо электронной почтой, ты должен написать заявление начальнику, где ты объяснишь, кому это письмо и для чего. Интернет «под расписку» есть только у избранных. Слава богу, хоть внутренняя сеть есть.

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

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

В результате даже молодняк пишет на Фортране, причем реально грамотных в программировании — единицы. Знаю, потому что подтягивал по программированию одного парня лет 25, который был мне отцом зарекомендован как многообещающий.
Мой вердикт: там 80-е годы. Даже при том, что там неплохо платят, я не пойду туда ни за какие коврижки.

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

Просто люди имеют свойство быть очень консервативными в областях, не являющихся их основным «коньком».


(Илья Майзус. Оригинал комментария.)

Самое главное здесь, что компьютер это просто Большой Калькулятор. А раз так, то и знать о нём можно не больше чем заслуживает его младший родственник — «калькулятор карманный». Да, именно так его и используют. В разных областях. Давайте на секунду отвлечемся и заглянем в мир физики. Посмотрим, как находит подтверждение очередная теория. Для этого мне вновь придется привести большую цитату. Источником является книга Брайана Грина «Элегантная вселенная (суперструны, скрытые размерности и поиски окончательной теории)» [1]:

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

Расчет, который нужно было провести, грубо говоря, сводился к определению массы конкретной частицы, являющейся колебательной модой струны при ее движении во вселенной, компоненту КалабиЯу которой мы изучали всю осень. Мы надеялись, что в соответствии с выбранной нами стратегией масса окажется точно такой же, что и масса в случае многообразия КалабиЯу, возникшего после флопперестройки с разрывом пространства. Последнюю массу вычислить было легко, и мы сделали это несколькими неделями раньше. Ответ оказался равным 3 в определенной системе единиц, которой мы пользовались. А так как сейчас проводился численный расчет на компьютере, то ожидаемый результат должен был быть близким к числу 3, чтото вроде 3,000001 или 2,999999; отличие от точного ответа объяснялось бы ошибками округления.

Моррисон сел за компьютер. Его палец завис над клавишей «Enter». Напряжение нарастало. Моррисон выдохнул «поехали» и запустил программу. Через пару секунд компьютер выдал ответ: 8,999999. Мое сердце упало. Неужели действительно флопперестройки с разрывом пространства нарушают зеркальную симметрию, а значит, вряд ли существуют в реальности? Но в следующее же мгновение мы сообразили, что здесь какаято глупая ошибка. Если в массах частиц на двух многообразиях действительно есть отличие, почти невероятно, что компьютер выдал бы результат, столь близкий к целому числу. Если наши идеи неверны, то с тем же самым успехом компьютер мог бы выдать ответ, состоящий из совершенно случайных цифр. Мы получили неправильный ответ, но неправильность его была такого вида, из которого напрашивался вывод о том, что гдето мы допустили банальную ошибку. Аспинуолл и я подошли к доске, и моментально ошибка была найдена: мы забыли множитель 3 в «простом» вычислении несколько недель назад, так что правильный результат должен был равняться 9. Поэтому ответ компьютера — это как раз то, на что мы надеялись.

Конечно, совпадение результата после того, как найдена ошибка, является лишь наполовину убедительным. Если известен желаемый результат, очень легко найти способ его получить. Нам срочно требовался другой пример. Имея все необходимые программы, придумать его не представляло сложности. Мы вычислили массу еще одной частицы на верхнем многообразии КалабиЯу, на этот раз с особой тщательностью, чтобы избежать еще одной ошибки. Ответом было число 12. Мы снова окружили компьютер и запустили программу. Через несколько секунд был получен ответ 11,999999. Согласие. Мы доказали, что предполагаемое зеркальное пространство является зеркальным пространством, и флопперестройки с разрывами пространства являются частью теории струн.

Я вскочил со стула и, опьяненный победой, сделал круг по комнате. Моррисон, сияя, сидел за компьютером. И только реакция Аспинуолла была нестандартной. «Здорово. Я и не сомневался, что все так и будет, — спокойно сказал Аспинуолл. — А где мое пиво?»

Я верю, что они гении. Но представим, что таким подходом вычислялось значение интеграла обыкновенными студентами. Не думаю, что тогда программисты посчитали бы такой подход серьезным. А если бы программа сразу выдала 3? Что тогда? Ошибка в программе посчиталась бы за доказательство? Думаю, потом бы ошибка всплыла при перепроверке ими же или другими учеными. Но все равно, «идеальный сферический программист в вакууме» испуган от такого подхода.

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

Быть может пора что-то менять?

Я имею права сам себе наклеить пластырь. Могу порекомендовать, что, по моему мнению, стоит выпить при простуде. Но не более. Я не могу сверлить зуб или выписать рецепт.

Быть может, когда ответственность создаваемой программной системы выходит за определённые рамки, её разработчики тоже должны подтверждать свою квалификацию?

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

Пример опасности


Думаю, многим мои переживания кажутся слишком абстрактами. Поэтому, давайте рассмотрим что-то из практики. Есть открытый пакет Trans-Proteomic Pipeline (TPP), для решении задач в сфере биологии. Его явно используют. Используют его те, кто разрабатывает и возможно сторонние организации. Мне кажется, наличие любой в нем ошибки, уже потенциальная проблема. А есть ли в нём ошибки? Да есть. И появляются всё новые. Год назад, мы проверяли этот проект и написали заметку "Проверка проекта Trans-Proteomic Pipeline (TPP)".

Изменилось что-то с тех пор? Ничего не изменилось. Проект продолжает развиваться и обрастать новыми ошибками. Большой Калькулятор победил. Разработчики не заняты написанием высококачественного проекта с минимально возможным количеством ошибок. Они просто решают задачи. Будь иначе, они бы как-то отреагировали на предыдущую статью и задумались над внедрением каких-то инструментов статического анализа. Я не имею в виду, что они были обязаны выбрать PVS-Studio. Есть много других статических анализаторов кода. Важно то, что в ответственном приложении продолжают появляться типичнейшие ошибки. Давайте посмотрим, что есть новенького.

1. Какой-то неумеха продолжает писать неправильные циклы


В предыдущей статье, я уже писал про некорректные условия в циклах. Есть такие ошибки и новой версии пакета.
double SpectraSTPeakList::calcDot(SpectraSTPeakList* other) {
  ....
  for (i = this->m_bins->begin(), j = other->m_bins->begin(); 
       i != this->m_bins->end(), j != other->m_bins->end();
       i++, j++) {
    d = (*i) * (*j);
    dot += d; 
  }
  ....
}

Диагностическое сообщение PVS-Studio: V521 Such expressions using the ',' operator are dangerous. Make sure the expression is correct. spectrastpeaklist.cpp 504

В проверке «i != this->m_bins->end(), j != other->m_bins->end()», выражение стоящее до запятой ничего не проверяет. Оператор запятая ',' используется для выполнения стоящих по обе стороны от него выражений в порядке слева направо и возвращает значение правого выражения. Корректная проверка должна выглядеть так:
i != this->m_bins->end() && j != other->m_bins->end()

Аналогичные ляпы можно увидеть здесь:
  • spectrastpeaklist.cpp 516
  • spectrastpeaklist.cpp 529
  • spectrastpeaklist.cpp 592
  • spectrastpeaklist.cpp 608
  • spectrastpeaklist.cpp 625
  • spectrastpeaklist.cpp 696

2. Разыменовывание нулевого указателя


Такая ошибка не приведет к неправильным результатам вычислений. Будет падание программы, что намного лучше. Однако, не написать про эти ошибки будет тоже странно.
void ASAPRatio_getDataStrctRatio(dataStrct *data, ....)
{
  ....
  int *outliers, *pepIndx=NULL;
  ....
  //pepIndx не изменяется
  ....
  if(data->dataCnts[i] == 1 && pepIndx[i] == 0)  
     data->dataCnts[i] = 0;
  ....
}

Диагностическое сообщение PVS-Studio: V522 Dereferencing of the null pointer 'pepIndx' might take place. asapcgidisplay2main.cxx 534

Здесь также разыменовывается нулевой указатель:
  • Pointer 'peptides'. asapcgidisplay2main.cxx 556
  • Pointer 'peptides'. asapcgidisplay2main.cxx 557
  • Pointer 'peptides'. asapcgidisplay2main.cxx 558
  • Pointer 'peptides'. asapcgidisplay2main.cxx 559
  • Pointer 'peptides'. asapcgidisplay2main.cxx 560
  • Pointer 'pepIndx'. asapcgidisplay2main.cxx 569

3. Неочищенные массивы


static void clearTagNames() {
   std::vector<const char *>ptrs;
   for (tagname_set::iterator i = tagnames.begin();
        i!=tagnames.end();i++) {
      ptrs.push_back(*i);
   }
   for (tagname_set::iterator j = attrnames.begin();
        j!=attrnames.end();j++) {
      ptrs.push_back(*j);
   }
   tagnames.empty();
   attrnames.empty();
   for (size_t n=ptrs.size();n--;) {
      delete [] (char *)(ptrs[n]); // cast away const
   }
}

Анализатор заметил здесь сразу два неочищенных массива:

V530 The return value of function 'empty' is required to be utilized. tag.cxx 72

V530 The return value of function 'empty' is required to be utilized. tag.cxx 73

Вместо функции empty() следует вызывать функцию clear().

4. Неинициализированные объекты классов


class ExperimentCycleRecord {
public:
  ExperimentCycleRecord() {
    ExperimentCycleRecord(0,0,0,True,False);
  }
  ExperimentCycleRecord(long lExperiment, long lCycleStart,
                        long lCycleEnd, Boolean bSingleCycle,
                        Boolean bRangleCycle)
  {
    ....
  }
  ....
}

Диагностическое сообщение PVS-Studio: V603 The object was created but it is not being used. If you wish to call constructor, 'this->ExperimentCycleRecord::ExperimentCycleRecord(....)' should be used. mascotconverter.cxx 101

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

Строчка «ExperimentCycleRecord(0,0,0,True,False);» место вызова другого конструктора, создает временный объект, который будет разрушен. Подробнее этот паттерн ошибки я рассматривал в статье "Не зная брода, не лезь в воду — часть первая".

Аналогичные неправильные конструкторы можно найти здесь:
  • asapratiopeptideparser.cxx 57
  • asapratiopeptidecgidisplayparser.cxx 36
  • cruxdiscrimfunction.cxx 36
  • discrimvalmixturedistr.cxx 34
  • mascotdiscrimfunction.cxx 47
  • mascotscoreparser.cxx 37
  • tandemdiscrimfunction.cxx 35
  • tandemkscoredf.cxx 37
  • tandemnativedf.cxx 37

5. Комментарии, которые нарушили логику


int main(int argc, char** argv) {
  ....
  if (getIsInteractiveMode())  
    //p->writePepSHTML();
  //p->printResult();

  // regression test?
  if (testType!=NO_TEST) {
     TagListComparator("InterProphetParser",testType,
       outfilename,testFileName);
  ....
}

Диагностическое сообщение PVS-Studio: V628 It's possible that the line was commented out improperly, thus altering the program's operation logics. interprophetmain.cxx 175

После оператора 'if' были закомментированы строчки, выполняющие действия. В результате, логика работы программа изменилась не так, как планировал программист. Он хотел, чтобы при выполнении условия ничего не происходило. Вместо этого, оператор 'if' оказывает влияние на код, расположенный ниже. Результат — запуск тестов зависит теперь не только от условия «testType!=NO_TEST», но и от условия «getIsInteractiveMode()». Тест может ничего не тестировать. Крайне рекомендую не полагаться полностью на одну методологию тестирования (например, TDD).

6. Опечатки


Опечатки есть везде и всегда. Не страшно, если из-за такой ошибки в игре у вас станет меньше жизней после взрыва, чем планировалось. А что означают неправильные данные при химических расчетах?
void ASAPRatio_getProDataStrct(proDataStrct *data, char **pepBofFiles) 
{
  ....
  if (data->indx == -1) {
    data->ratio[0] = -2.;
    data->ratio[0] = 0.;
    data->inv_ratio[0] = -2.;
    data->inv_ratio[1] = 0.;
    return;
  }
  ....
}

Диагностическое сообщение PVS-Studio: V519 The 'data->ratio[0]' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 130, 131. asapcgidisplay2main.cxx 131

Случайно два раза записали значения в одну и ту же переменную. Должно было быть:
data->ratio[0] = -2.;
data->ratio[1] = 0.;

А потом, ещё копировали этот фрагмент в другие местам программы:
  • asapcgidisplay2main.cxx 338
  • asapcgidisplay2main.cxx 465
  • asapratioproteincgidisplayparser.cxx 393
  • asapratioproteincgidisplayparser.cxx 518

7. Сравнение знаковых и беззнаковых чисел.


Сравнивать знаковые и беззнаковые числа надо уметь. В обыкновенных калькуляторах нет беззнаковых чисел. А в языке Си++ есть.
size_type size() const;
void computeDegenWts()
{
  ....
  int have_cluster = 0;
  ....
  if ( have_cluster > 0 && ppw_ref.size() - have_cluster > 0 )
  ....
}

Диагностическое сообщение PVS-Studio: V555 The expression 'ppw_ref.size() — have_cluster > 0' will work as 'ppw_ref.size() != have_cluster'. proteinprophet.cpp 6767

Хотелось выполнить проверку «ppw_ref.size() > have_cluster». Но получилось совсем иное.

Для простоты, пусть у нас тип 'size_t' будет 32-битным. Предположим, функция «ppw_ref.size()» вернёт число 10, а переменная have_cluster равна 15. Функция ppw_ref.size() возвращает беззнаковый тип 'size_t'. По правилам языка Си++ перед вычитанием правый оператор в операции минус тоже станет иметь тип 'size_t'. Пока всё хорошо. Слева у нас 10u, справа 15u.

Вычитаем:

10u — 15u

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

Это значит, что 10u — 15u = FFFFFFFBu. А, как известно, 4294967291 больше 0.

Бунт Большого Калькулятора удаётся. Мало написать правильный теоретический алгоритм. Надо ещё написать правильный код.

Схожая по духу ошибка присутствует здесь:
double SpectraSTPeakList::calcXCorr() {
  ....
  for (int tau = -75; tau <= 75; tau++) {
  
    float dot = 0.0;
    for (unsigned int b = 0; b < numBins; b++) {
      if (b + tau >= 0 && b + tau < (int)numBins) {
        dot += (*m_bins)[b] * theoBins[b + tau] / 10000.0;
      }
    }
    ....
  ....
}

Диагностическое сообщение PVS-Studio: V547 Expression 'b + tau >= 0' is always true. Unsigned type value is always >= 0. spectrastpeaklist.cpp 2058

Как видно из кода, переменная 'tau' принимает значения в диапазоне [-75, 75]. Чтобы не выйти за границу массива имеется проверка: b + tau >= 0. Я думаю, вы уже поняли, что эта проверка не работает. Переменная 'b' имеет тип 'unsigned'. Это значит, что и выражение «b + tau» имеет тип unsigned. Значение типа unsigned всегда больше или равно 0.

8. Странный цикл


const char* ResidueMass::getStdModResidues(....) {
  ....
  for (rmap::const_iterator i = p.first; i != p.second; ++i) {
    const cResidue &r = (*i).second;
    if (r.m_masses[0].m_nterm) {
        n_term_aa_mod = true;
    } else if (r.m_masses[0].m_cterm) {
        c_term_aa_mod = true;
    }
    return r.m_residue.c_str();
  }

  if(! strcmp(mod, "+N-formyl-met (Protein)")) {
    return "n";
  } if (! strcmp(mod, "13C6-15N2 (K)")) {
    return "K";
  } if (! strcmp(mod, "13C6-15N4 (R)")) {
    return "R";
  ....  
}

Предупреждение выданное PVS-Studio: V612 An unconditional 'return' within a loop. residuemass.cxx 1442

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

9. Грубые вычисления


double RTCalculator::getUsedForGradientRate() {
  if (rts_.size() > 0)
    return used_count_ / rts_.size();
  return 0.;
}

Предупреждение выданное PVS-Studio: V636 The 'used_count_ / rts_.size()' expression was implicitly casted from 'int' type to 'double' type. Consider utilizing an explicit type cast to avoid the loss of a fractional part. An example: double A = (double)(X) / Y;. rtcalculator.cxx 6406

Так как функция возвращает значения типа double, то разумно предположить следующее.

Если переменная 'used_count_' равна 5, а функция rts_.size() вернет 7, то результат должен быть приблизительно равен 0,714. Вот только функция getUsedForGradientRate() в этом случае вернет 0.

Переменная 'used_count_' имеет тип int. Функция rts_.size() тоже возвращает значение типа 'int'. Происходит целочисленное деление. Результат очевиден. Он равен нулю. Затем ноль неявно приводится к типу double. Но это уже не важно.

Чтобы исправить ситуацию, можно написать так:
return static_cast<double>(used_count_) / rts_.size();

Аналогичные недочеты:
  • cgi_pep3d_xml.cxx 3203
  • cgi_pep3d_xml.cxx 3204
  • asapratiopeptideparser.cxx 4108

10. Великий и могучий Copy-Paste


Функция setPepMaxProb() содержит несколько больших однотипных блоков. Сразу чувствуется, что без методики Copy-Paste здесь не обошлось. А как результат, код содержит ошибку. Мне пришлось ОЧЕНЬ сильно сократить код примера. В сокращенном варианте ошибка легко видна. В коде программы, заметить её почти не реально. Да, это реклама инструментов статического анализа вообще, и PVS-Studio в частности.
void setPepMaxProb( bool use_nsp, bool use_fpkm, 
  bool use_joint_probs, bool compute_spectrum_cnts )
{  
  double prob = 0.0;
  double max2 = 0.0;
  double max3 = 0.0;
  double max4 = 0.0;
  double max5 = 0.0;
  double max6 = 0.0;
  double max7 = 0.0;
  ....
  if ( pep3 ) { ... if ( use_joint_probs && prob > max3 ) ... }
  ....
  if ( pep4 ) { ... if ( use_joint_probs && prob > max4 ) ... }
  ....
  if ( pep5 ) { ... if ( use_joint_probs && prob > max5 ) ... }
  ....
  if ( pep6 ) { ... if ( use_joint_probs && prob > max6 ) ... }
  ....
  if ( pep7 ) { ... if ( use_joint_probs && prob > max6 ) ... }
  
  ....
}

V525 The code containing the collection of similar blocks. Check items 'max3', 'max4', 'max5', 'max6', 'max6' in lines 4664, 4690, 4716, 4743, 4770. proteinprophet.cpp 4664

Предупреждение выданное PVS-Studio: V525 The code containing the collection of similar blocks. Check items 'max3', 'max4', 'max5', 'max6', 'max6' in lines 4664, 4690, 4716, 4743, 4770. proteinprophet.cpp 4664

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

11. Указатель не всегда инициализируется


int main(int argc, char** argv) {
  ....
  ramp_fileoffset_t *pScanIndex;
  ....
  if ( (pFI=rampOpenFile(mzXmlPath_.c_str()))==NULL) {
    ....
  } else {
    ....
    pScanIndex = readIndex(pFI, indexOffset, &iAnalysisLastScan);
    ....
  }
  ....
  if (pScanIndex != NULL)
    free(pScanIndex);

  return 0;
}

Предупреждение выданное PVS-Studio: V614 Potentially uninitialized pointer 'pScanIndex' used. sqt2xml.cxx 476

Эта программа может в конце аварийно завершиться, если функция rampOpenFile() вернёт NULL. Не критично, но неприятно.

Другая переменная, которая может оказаться не инициализированной, находится здесь:
  • Potentially uninitialized pointer 'fp_' used. dta-xml.cpp 307

12. Нет виртуального деструктора


class DiscriminantFunction {
public:
  DiscriminantFunction(int charge);
  virtual Boolean isComputable(SearchResult* result) = 0;
  virtual double getDiscriminantScore(SearchResult* result) = 0;
  virtual void error(int charge);
protected:
  int charge_;
  double const_;
}; // class

class CometDiscrimFunction : public DiscriminantFunction;
class CruxDiscrimFunction : public DiscriminantFunction;
class InspectDiscrimFunction : public DiscriminantFunction;
.....

class DiscrimValMixtureDistr : public MixtureDistr {
  ....
  DiscriminantFunction* discrim_func_;
  ....
};

DiscrimValMixtureDistr::~DiscrimValMixtureDistr() {
  delete[] posinit_;
  delete[] neginit_;
  delete discrim_func_;
}

Предупреждение выданное PVS-Studio: V599 The virtual destructor is not present, although the 'DiscriminantFunction' class contains virtual functions. discrimvalmixturedistr.cxx 206

От класса DiscriminantFunction наследуется множество классов. Например, наследником является класс DiscrimValMixtureDistr. Деструктор этого класса освобождает память, а, следовательно, очень желательно его вызывать. К сожалению, деструктор в классе DiscriminantFunction не объявлен виртуальным, со всеми вытекающими последствиями.

13. Разное


Можно найти массу недочетов, которые не приведут к серьезным последствиям, но их присутствие в коде неприятно. Есть и просто подозрительные, но не обязательно ошибочные места. Вот одно из них:
Boolean MixtureModel::iterate(int counter) {
  ....
  if (done_[charge] < 0) {
    done_[charge];
  }
  else if (priors_[charge] > 0.0) {
    done_[charge] += extraitrs_;
  }
  ....
}

Предупреждение выданное PVS-Studio: V607 Ownerless expression 'done_[charge]'. mixturemodel.cxx 1558

Что это? Недописанный код? Или хотели подчеркнуть, что не надо ничего делать, если выполнится условие «done_[charge] < 0»?

А вот неправильное освобождение памяти. С большой вероятностью страшных последствий не будет, но этот код с запахом.
string Field::getText(....)
{
  ....
  char* pepString = new char[peplen + 1];
  ....
  delete pepString;
  ....
}

Предупреждение выданное PVS-Studio: V611 The memory was allocated using 'new T[]' operator but was released using the 'delete' operator. Consider inspecting this code. It's probably better to use 'delete [] pepString;'. pepxfield.cxx 1023

Здесь следовало написать «delete [] pepString». Такое место далеко не одно:
  • cruxdiscrimvalmixturedistr.cxx 705
  • cruxdiscrimvalmixturedistr.cxx 715
  • mascotdiscrimvalmixturedistr.cxx 426
  • mascotdiscrimvalmixturedistr.cxx 550
  • mascotdiscrimvalmixturedistr.cxx 624
  • phenyxdiscrimvalmixturedistr.cxx 692
  • probiddiscrimvalmixturedistr.cxx 487
  • probiddiscrimvalmixturedistr.cxx 659
  • tandemdiscrimvalmixturedistr.cxx 731
  • tandemdiscrimvalmixturedistr.cxx 741


А вот реализация оператора "--". Видимо он не используется нигде. Иначе ошибка бы быстро выявилась.
CharIndexedVectorIterator operator++(int)
{  // postincrement
  CharIndexedVectorIterator _Tmp = *this;
  ++m_itr;
  return (_Tmp);
}

CharIndexedVectorIterator& operator--()
{  // predecrement
  ++m_itr;
  return (*this);
}

Предупреждение выданное PVS-Studio: V524 It is odd that the body of '--' function is fully equivalent to the body of '++' function. charindexedvector.hpp 81

Операторы "--" и "++" реализованы одинаково. А дальше, наверное, скопировали:
  • charindexedvector.hpp 87
  • charindexedvector.hpp 159
  • charindexedvector.hpp 165
Продолжать дальше не буду. Это не так уж всё интересно, да и статья затянулась. Как всегда прошу разработчиков, не останавливаться на правке перечисленных недоработок. Скачайте и проверьте самостоятельно проект с помощью PVS-Studio. Я мог пропустить многие ошибки. Мы готовы выделить бесплатный ключ на некоторое время.

Резюмирую


К сожалению, статья получилась путанной. Так что же хотел сказать автор? Попробую совсем кратко повторить, какие мысли я хотел донести.
  1. Мы все больше используем и доверяем программам, выполняющим расчёты и моделирующие процессы.
  2. Программы сильно усложняются. Профессиональным программистам очевидно, что нельзя создавать пакет численного моделирования так же, как использовать программируемый калькулятор. Рост сложности ведёт к экспоненциальному росту количества ошибок [2].
  3. Получается, что физики/биологи/медики уже не могут по-прежнему просто что-то посчитать. Нельзя игнорировать рост сложности программ и последствия неправильных вычислений в силу неполного владения языком программирования.
  4. В статье я привел аргументы, что всё обстоит именно так. Первая цитата — люди используют компьютер, как калькулятор. Вторая цитата — да, именно как калькулятор. Примеры ошибок — да, и действительно допускают ошибок. Мои опасения обоснованы.


И что предлагается делать?

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

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

Дополнительные ссылки


  1. Брайан Грин «Элегантная вселенная (суперструны, скрытые размерности и поиски окончательной теории. ISBN 978-5-453-00011-1 (УРСС), ISBN 978-5-397-01575-2 (Книжный дом „ЛИБРОКОМ“)
  2. Андрей Карпов. Ощущения, которые подтвердились числами. http://www.viva64.com/ru/b/0158/
Автор: @Andrey2008
PVS-Studio
рейтинг 460,18
Ищем ошибки в C, C++ и C# на Windows и Linux

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

  • +8
    Сам из семьи таких вот физиков-программистов. Для своего времени эти люди очень неплохо знали язык, даже находили ошибки в компиляторах. Но это на всяких БЭСМ-6. Те программы были по сути более лаконичной версией ассемблера. С тех пор всё изменилось, их программы (очень хорошие для своего времени) больше никому не нужны, а переписывать их в лоб не имеет никакого смысла. И компьютерная грамотность, да.

    Контакт поколений произошёл когда я начал пробовать Linux. Оказалось, что папа 20 лет назад тоже пользовался vi под советским клоном UNIX :)
  • +3
    никто не обращает внимания на рост сложности программ.… далеко не все знают и задумываются о том, что рост размера программы ведет к нелинейному росту числа ошибок


    Мне кажется что мы с вами в разных мирах живем.
    • +4
      а в вашем мире как — рост линейный или обратно пропорциональный?
      • 0
        С одной стороны, число ошибок на строку кода — величина практически постоянная для отдельного программиста. С другой стороны — с увеличением размера кода увеличивается вероятность косвенных ошибок. С третьей стороны — бывает, конечно, по разному, но зачастую с ростом размера кода, который пишется, растет и размер кода, который тем или иным способом генерируется и имеет минимум ошибок. Второй и третий факторы, по идее, примерно друг друга компенсируют и остается просто линейный рост.
        • 0
          Почему же большие программные проекты со временем «разваливаются», если там всего лишь линейный рост?
          • 0
            всего лишь линейный рост

            Это та ситуация, когда «достаточно одного раза». Ну и, вообще, они обычно и не разваливаются — просто существуют в почти-рабочем состоянии.
            • 0
              Есть мнение, что рост количества ошибок напрямую с размером кода не связан. Он связан с «кривостями» архитектуры. А вот оные кривости допускаются тем чаще, чем больше проект. В огромном проекте архитекторы должны быть часто семи пядей во лбу. И если мы предположим, что количество «костылей» пропорционально количеству кода, а количество ошибок на строку кода пропорционально количеству «костылей», то вот мы уже получили нелинейную зависимость. Так, на пальцах.

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

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

            pps
            Соответственно, большие проекты разваливаются из-за того, что добавление новой небольшой фичи (или исправление трудноуловимого глюка) по стоимости становится сравнимым с разработкой с нуля всего проекта.
      • +1
        никто не обращает внимания на рост сложности программ.… далеко не все знают и задумываются о том, что рост размера программы ведет к нелинейному росту числа ошибок


        Что то мне подсказывает, что прораммисты не от нечего делать стремятся упростить задачу и написать меньше кода.
      • +1
        <зануда mode> Если число ошибок более чем линейно растет от размера кода, то рано или поздно оно станет больше, чем битов в коде. Что сомнительно.
        • 0
          Асимптотически — да. Но код становится неподдерживаемым и технически непригодным значительно раньше ;)

          Как в том старом анекдоте; есть задача — юноша и девушка стоят друг от друга на расстоянии 2 метра. Юноша очень стеснителен и подходит к девушке медленно. За первую минуту он прошёл 1 метр, за вторую еще полметра, за третью — четверть… в общем, понятно. Вопрос: когда они встретятся?

          Ответ математика: никогда
          Ответ физика: в бесконечно удаленный момент времени
          Ответ инженера: через полчаса они окажутся достаточно близко друг к другу для реализации любых практических надобностей.
    • +2
      Не я с вами в разных мирах. Это мы и они в разных. :)

      Вы никогда не слышали историй, когда на крупном предприятии какая-то программа учета представляет собой один здоровый файл с кодом на несколько мегабайт? Типа, постепенно добавляли, добавляли…
      • –3
        Слышал. Но я имел в виду то, что всетаки большинство обращает внимние и задумывается.

        Дочитав до этой фраза я остановился и подумал, что это рекламная акция.
  • 0
    Вы предлагаете программистам в сторонних областях подтвержадать квалификацию в этих областях или использовать PVS-Studio?
    • +1
      Я бы не прочь продавать им PVS-Studio. Но это нереально. Как раз наоборот, PVS-Studio покупают те, кто очень хорошо разбирается в создании программного обеспечения. Они понимают, в чем ценность статического анализа и что лишняя проверка не повредит.

      Так что эта статья скорее просто мысли в слух. И вдруг, кто-то прочитает и задумается. А то ли и так ли он делает в своем НИИ/заводе/т.д.
      • +1
        хочу добавить, что программы, которые используются для вычислений в серьезных научных и медецинских организациях — обязательно должны проходить серьезное тестирование, которое должно исключить опечатки и ошибки в синтаксе. Даже в вашем примере, физики прежде чем поверить программе — проверили еще одно значение. Так что может все не так фатально?
  • +4
    Какая знакомая проблема :) Поясню, работая в инженерных изысканиях, мы постоянно сталкиваемся с работой таких «калькуляторов», причем в некоторых случаях (например, обработка GNSS-данных) выполнить итерации вручную не представляется возможным. Для контроля используется 2-3 программных идентичных продукта, результаты которых сравниваются и применяются в зависимости от степени «разброса». Иногда приходится подключать «тяжелую артиллерию» в виде научно-коммерческого Bernese, выпускаемого астрономическим институтом Берна…
  • +3
    Как хорошо было бы не использовать C++ для программирования калькулятора. Жалко, что мы живём в реальном мире и люди зачастую не хотят использовать правильные инструменты для решения их задач.
    • +1
      Какие инструменты и языки предлагаете для написания научных программ?
      • –1
        Matlab, например. На нем даже UI для этих научных программ вполне адекватно можно делать.
        • 0
          Расчеты в матлабе медленнее на порядок/порядки
          • +1
            Зависит от техники реализации алгоритма, если всё делать только с помощью матричных операций, скорость будет очень высокой.
            • +1
              Проблема пакетов типа Matlab/Maple в полной непрозрачности того, что они делают. Для анализа типа «реши мне диффур аналитически» они годятся хорошо. Но если вы хотите иметь полный контроль за методом решения (а в любой научной и тем более инженерной задаче вы его хотите), придется кодить ручками.
              • +1
                При всем при этом, Matlab прекрасно подходит для разработки прототипов. Не так уж часто необходимо получать максимальную производительность с первой же итерации.
      • –6
        Отлично подходит язык C#. Несмотря на проседание итоговой производительности по сравнению с кодом на чистом C (примерно в полтора-два раза на математических задачах, в своё время собственноручно измерял), в нем есть строгая типизация, гарантирующая отсутствие многих проблем, великолепный отладчик, отсутствие необходимости явно оперировать с памятью и возможность создавать пользовательский интерфейс довольно легко и быстро.

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

        Ну и люди, беспокоящиеся о лицензионной чистоте, добавят сюда, что C# — детище «корпорации зла». Хотя он стандартизован, а mono издан под GPL…

        Я на C# писал курсовую — рассчитывал некоторые процессы в полупроводниках при низких температурах. Всё было легко — и считать, и диаграммы красивые рисовать, и графики строить.
        • +4
          Маленькие проекты и курсовые — нормально. Большие не получится, т.к. есть большая разница между расчетами день или два дня, неделя или полмесяца… Особенно, если тестов надо провести много.
          Суперкомпьютеры как правило работают на Linux и как программу на C# поднять на суперкопьютере в консоли? И какого будет проседание производительности не в родной платформе?
          • –4
            есть большая разница между расчетами день или два дня, неделя или полмесяца…


            1. Если у вас такой пасчет, что даже падение производительности в 2 раза по сравнению с нативной — беда, значит у вас нет выбора, на чём писать. Пишите на C++ (надеюсь, Фортран в качестве реальной альтернативы не рассматривается — те кривые костыли, которыми туда приделали ООП, вызывают слёзы). А что касается тестов — основная потеря времени при написании сложного расчетного софта — его отладка, а не исполнение. Если над программой работают 20 человек и им всем платят деньги, купить еще один компьютер и распараллелить задачу на 2 обычно удаётся. И, кстати, в этом случае лучше использовать язык современный, с нормальной поддержкой сети в стандартной библиотеке.

            2. Если всё же так остро проблема с производительностью не стоит, то
            Суперкомпьютеры как правило работают на Linux и как программу на C# поднять на суперкопьютере в консоли?

            Здесь я могу вам предложить пройти вот сюда: www.mono-project.com/Main_Page
            Кроссплатформенная имплементация CLR вместе со стандартными библиотеками позволит вам запустить программу хоть на суперкомпьютере, хоть на кластере. Разумеется, ее необходимо там иметь, но я это уже указывал как недостаток выше. В конце концов, если у вас такая серьёзная программа, то просто добавить в нее код mono и отправить на суперкомпьютер всё вместе — дело 1-2 дней. А производительность под .NET и под mono практически одинакова и отличается на 1-2%
            • –1
              которыми туда приделали ООП, вызывают слёзы
              Простите, а зачем для программы, связанной с вычислительной математикой, ООП?
              • +1
                Очень опасаюсь, что получу еще порцию негодования, начав объяснять свою точку зрения подробнее. Но всё же рискну.

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

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

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

                А теперь, когда метафора развёрнута, упростим ее.

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

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

                И вот эти, вторые работники — объекты. И именно чтобы их создать, нужно ООП.

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

                И еще пару слов в оправдание за мой заминусованный комментарий.

                Я не знаю, с какими проблемами придется столкнуться при использовании C# на суперкомпьютере. Это надо проверять. Я лишь пытался порекомендовать язык, достаточно мощный для любых практических целей и при этом помогающий хорошо защитить разработчика от случайных ошибок. Подробнее на эту тему я отписался ниже.
                • 0
                  И вот эти, вторые работники — объекты. И именно чтобы их создать, нужно ООП.


                  Собственно, исходя из вашего комментария, у меня ещё сильнее выросло ощущение того, что ООП в вычислительных задачах (причем не суть важно — на суперкомпьютерах или на микроконтроллерах) — исключительно перенос классических практик разработки general purpose software на специфическую предметную область.

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

                  Все вышенаписанное, имхо, естественно.

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

                    Но сперва обращу ваше внимание вот на что:
                    Как средство организации кода — да, использовать ООП можно, но не более.

                    Возможно, эта мысль покажется вам непривычной, но всё — и функции, и классы — не более, чем средство организации кода. Процессор же, как и 30 лет назад, умеет исполнять лишь совершенно неструктурированные дурацкие команды типа mov, add или cmp. А всё остальное люди придумали для себя. Если же вы намекаете этой фразой на то, что надо использовать классы как «коробочки», в которые просто напихиваются функции, то я вас заклинаю от такого подхода — пишите лучше просто на функциях. ООП — это не ключевое слово «class» в языке. ООП — это, прежде всего, манера мышления, при которой разработчик, разрабатывая и тестируя отдельный класс, выбрасывает из своего сознания все прочие, вернее, он выбрасывает их внутреннее устройство. И именно для того, чтобы подобный подход работал, существует инкапсуляция. Без нее один объект может внезапно изменить внутреннее состояние другого в неожиданный момент. И такая программа будет неуправляемо глючить. Поэтому если вы собираетесь создавать в ваших классах публичные внутренние поля, лучше не используйте классы вовсе. Вам же проще будет.

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


                    Давайте разберем это утверждение по частям. Во-первых, понятие «высокий уровень инкапсуляции» считаю бессмысленным примерно как «высокий уровень беременности» (применительно к одной женщине). Инкапсулированность класса — понятие модальное. Либо она есть, либо нет. Если класс допускает возможность менять своё внутреннее состояние без своего ведома, значит он не инкапсулирован (читай — не закрыт).

                    Теперь о геттерах/сеттерах, так как это — извечная больная тема. Вообще говоря, сам факт необходимости создавать тривиальные геттеры/сеттеры в современных объектно-ориентированных языках является сигналом того, что языки эти несовершенны. И разработчики этих языков стремятся с этой проблемой бороться.

                    Во-первых, смею заверить вас, что любой более-менее солидный компилятор любой ваш геттер/сеттер непременно сделает inline-методом, то есть код val = 3 и setVal(3) превраитися в один и тот же бинарный код. Так что по поводу производительности можете быть спокойны. В C++ — точно, в C# и Java — скорее всего.

                    Во-вторых, в C# существует элегантнейшая вещь, называемая "свойствами", которая позволяет сделать так, чтобы вызов getter-а выглядел просто как обращение к переменной (равно как и вызов setter-а).

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

                    class Point
                    {
                        private double x, y;
                        public Point(double x, double y)
                        {
                            this.x = x; this.y = y;
                        }
                        public double X
                        {
                            get { return x; }
                        }
                        public double Y
                        {
                            get { return y; }
                        }
                    }
                    


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

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

                    классических практик разработки general purpose

                    Я не считаю, что есть такое понятие, как практики general purpose. Есть большие программы и маленькие. Маленькие можно писать как попало и на чем угодно — вы легко сможете их и проверить, и отладить. А вот большие надо писать очень аккуратно и вдумываться прежде всего в их организацию. А то вызовете на белый свет Летающего Макаронного Монстра.  ;)
        • +1
          Есть библиотеки, к примеру ILNumerics, которые могут существенно увеличить скорость алгоритмов, написанных на C#, за счёт оптимизаций управления памятью. К примеру, при сравнительно больших наборах данных, скорость может приближаться к реализации на FORTRAN. ilnumerics.net/blog/fast-faster-performance-comparison-c-ilnumerics-fortran-matlab-and-numpy-part-ii/
          • –1
            Да. А еще можно самый внутренний цикл счетного ядра, который отрабатывает за пару секунд, обложить юнит-тестами и переписать на C. А код этот дёрнуть через P/Invoke. И таким образом получить производительность, отличающуюся от нативной на проценты.

            Разумеется, так можно сделать не всегда, но во многих случаях.
      • 0
        язык Ада, Фортран. Хотя не в языках большей частью дело.
    • +2
      Я считаю, что надо создать высокопроизводительную базу (например, на C/C++) — качественные библиотеки символьных вычислений, работы с массивами, сортировки, и т.д., а чтобы потом «прикладные программисты» вызывали их из чего-то более лёгкого и высокоуровнего (Python?)

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

      И тогда выпускник такой школы не будет опускать ручки: "(Word/3dMax/AutoCAD) это не умеет, значит это невозможно", а будет брать инициативу в свои руки, создавать скрипты, писать плагины и т.д. (а для этого надо создать вышеупомянутые библиотеки для всего спектра задач, решаемых с помощью компьютера: от офисных редакторов до физического моделирования, от обработки видео и 3d-моделлинга до систем распознавания/синтеза речи). Разумеется это должно быть всё open source. Вот так я вижу идеальное будущее образования в обществе, которое уже немыслимо без компьютера в каждой авторучке.
      • +1
        Все уже придумано до вас.

        1. Boost — набор довольно таки высококачественных библиотек широкого спектра действия :) Часть кода которого идет дальше в стандарты. Почитайте про boost tr1.
        2. Есть достаточно инструментов связывания таких С++ библиотек, на вскидку SWIG. Не говоря уже о существующем Boost.Python
        3. Буст с каждой версией расширяется и модернизируется, добавляются новые библиотеки, новые алгоритмы работы с различными данными.

        Про образование согласен.
        • 0
          Я имел в виду более высокий уровень, чем boost (это уже во второй части моего комментария). Это скорее следует назвать API.

          P.S. А для чистого С такие штуки знаете?
      • 0
        Я считаю, что надо создать высокопроизводительную базу (например, на C/C++) — качественные библиотеки символьных вычислений, работы с массивами, сортировки, и т.д., а чтобы потом «прикладные программисты» вызывали их из чего-то более лёгкого и высокоуровнего (Python?)


        Именно так мы и делали. Хардкод на Fortran + симпатичный простой интерфейс на python.
      • +1
        Удивляюсь, как за столько лет существования программирования никто не собрался и не создал какой-нибудь глобальный консорциум, который бы занимался созданием глобального API на все случаи жизни.

        Описывал бы интерфейсы этих API, а потом бы принимал реализации, обложенные тестами со всех сторон, которые бы отличались лишь временем работы.

        Идеально чтобы это всё было формально верифицируемо.
        • +1
          Этой идее сто лет в обед. Проблема в том, что у разных задач разные требования.
          Кто-то готов пожертвовать точностью, кто-то скоростью.
          А так — миллионы строк на FORTRAN, mathlab, python (numpy) давно написаны и ими пользуются. Никто метод Гаусса или решение каких других уравнений скорее всего не будет писать, а если будет, значит требования особые.
  • –5
    Начало статьи очень интересное, плоть до того места, как мы переключаемся на PVS Studio.
    • +3
      Я полагаю, что это называется «от слов — к делу».
  • +8
    Пару лет назад имел счастье приобщиться к этому комплексу программ в одном Московском НИИ. В основном работал с TPP и X!Tandem.
    На самом деле спектр проблем даже шире, чем описан в статье и начинается он не с программ, а со стандартов. Как и в любой области, учёным довольно быстро пришлось придумать какой-либо стандарт на файлы, которыми будут обмениваться программы (например унифицированный формат результата эксперимента или масс-спектр). А так как им очень хотелось сохранить гибкость и большое разнообразие информации, которую каждый производитель аппаратов для экспериментов писал свою, то стали появляться такие шедевры как полуторагиговый XML в котором в BASE64 зашифрован массив структурок из 2х double'ов и тегами модель аппарата и его настройки. Унификации в итоге не получилось. Большинство программ падали в корку если на вход поступал файл от программы не «своего» производителя (pipline в названии продукта не с проста). Помнится я писал специальную программу-конвертр, которая конвертировала файл (удаляла лишние переносы строк!) чтобы программа из другого комплекса не падала.
    Как с этим жили? — На основные эксперименты довольно быстро появлялись коммерческие центры с платными закрытыми продуктами хорошего качества. SAAS, техподдержка и все дела.
    Также большие базы данных экспериментов, где все учёные делились исходными данными и выводами программ. В итоге усреднением результатов расчётов нескольких комплексов (и для этого начали писать софт) получалась какая-то истина.
  • +1
    Благо, научиться как-то программировать проще, чем разобраться в органической химии и принципах создания лекарств.
    ИМХО, не проще, сравнивать вообще некорректно. Просто в случае с медициной часто ответственность больше, поэтому туда кто попало не лезет. Но и в программировании есть области куда просто так не лезут. И даже не из-за сложности. В авионике, например, программы не супер сложные, однако требования к надежности и ответственность куда выше.
  • +8
    Тут сразу много причин и следствий в одном клубке. Попробуем разобраться.

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

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

    Физики используют С++ — вот это проблема. Но проблема-то надуманная. Заберите С++, верните Фортран — профит! Или не Фортран, а Питон, Лисп, R на худой конец. Маткад опять же.

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

    Программисты вроде как используют С++, но С++ против такого использования. Вот это действительно проблема, и ее можно решить. Статический анализ помогает. Кроме шуток, это очень полезная, хотя и недооцененная часть рабочего процесса. На самом деле, не обязательно даже покупать PVS, чтобы приобщиться к хорошему, достаточно сказать компилятору, к примеру, "-Wall -Wextra -pedantic -Weffc++" и перестать смотреть на него, как на телевизор. Все, что говорит компилятор, надо читать осмысленно — это наш первый и главнейший союзник в борьбе со злом.

    Потом можно попробовать и другие анализаторы, почему нет. Анализ делает за меня самую скучную и нервную работу, делает ее быстро, надежно и не просится в отпуск. Как можно не любить статический анализ? Но одного анализа недостаточно. Надо еще учиться писать код, да. Учиться надо много, упорно и постоянно. Пользуясь случаем, хочу напомнить, что новый стандарт С++11 сказочно хорош, и все мы там будем, так что кто еще не ознакомился — самое время.

    Еще надо уважать общепринятые стандарты программирования. Или хотя бы принять свой собственный стандарт и уважать его лично. Причем, не просто скобки / табы / с большой буквы, а нормальный такой стандарт уровня JSF AV C++.

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


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

      Это — сложная проблема. И начинать, мне кажется, надо с образования. Высшие (и даже средние) школы должны учить людей компьютерной грамотности не только для взгляда «с этой стороны монитора», но также и «с той».
      • +5
        Если лезть в образование, то там клубок причин еще больший. Еще более пугающий. Легко сказать, «средняя школа должна». А кто пойдет в среднюю школу учить грамотности? Вот даже если мы с вами сейчас соберемся и пойдем в школу вместо того, чтобы клавиатуры чесать, в России примерно пятьдесят тысяч школ, — мы до пятницы точно не управимся, а на субботу у меня планы.

        В высшем образовании чуть проще. Я даже поработал несколько лет на четверть ставки, — вполне себе годное хобби, пока основная работа позволяет. Но если уйти в академию с головой, придется забыть о карьере. А это дважды нехорошо: во-первых, зарплаты преподавателя не хватает на шлюх и кокаин, во-вторых, без связи с индустрией придется постепенно превратиться в физика и начать писать на Фортране.
        • 0
          Финансовых вопросов я в своих рассуждениях не касаюсь, так как неграмотен. Скажу только, что если государство хочет науку, оно должно хорошо вложиться в образование. Университет — это не базар. Он не может кормить сам себя. В самых капиталистических странах, где государство даже соц обеспечением не занимается (всё на страховках), насколько мне известно, образование всегда дотируется.

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

          И тогда если в своей карьере человек столкнётся с тем, что ему надо писать какие-то команды, он уже не будет «охотником с дубиной».
  • +14
    Не страшно, если из-за такой ошибки в игре у вас станет меньше жизней после взрыва, чем планировалось. А что означают неправильные данные при химических расчетах?

    Ну, возможно, из-за них у вас тоже станет меньше жизней после взрыва.
  • 0
    Значит жизнь игра:)
  • 0
    Справедливости ради, скалярное произведение (SpectraSTPeakList::calcDot), где '&&' был перепутан с ',' — всегда или работает правильно, или порождает мусор и/или стрельбу по памяти.
    Сама задача предполагает, что оба вектора имеют одинаковую размерность, поэтому что с запятой, что с конъюнкцией на правильных данных получится правильный результат.
    А на неправильных… ну, в любом случае неправильные данные ведут к неопределённому поведению. Так какая разница? :)
    • +2
      Исправление должно выглядеть так:
      assert(this->bins.size() == other->bins.size());
      for(
      i = this->bins.begin(), j = other->bins.begin();
      i != this->bins.end();
      ++i, ++j
      )
  • +2
    Как физик использующий калькулятор компьютер могу сказать, что не всё так страшно. Во-первых, большинство знакомых физиков программирует на довольно высоком уровне. И не только на Fortran, да. Во-вторых, любой результат подвергается куче проверок и перепроверок. В-третьих, если есть баги (а они всегда есть) то в большинстве случаев ошибка очевидна. Например, есть параметр который для кристалла равен 0, для газа 5, и если внезапно для жидкости он будет равен 100500, то это очевидно не гениальное открытие, а косяк в коде. В-четвертых, большинство вычислений дублируются (иногда полностью, иногда с немного измененными параметрами) коллегами из других институтов, и если что-то не так, это довольно быстро станет заметно. Ну и в-пятых, заверю вас, что между подсчетом того как какой-нибудь пептид влияет на почки и выпуском на рынок таблеток с ним, есть довольно большой промежуток, как во времени, так и в дополнительных проверках.
  • 0
    Вы не упомянули важный фактор — время. Физик может изучить Lisp, узнать, что такое виртуальный деструктор и научиться делать rebase в git. Ничего принципиально сложного в этом нет, это не квантовая механика, просто нужно время. Примерно лет десять, как в любой другой области. Те же десять лет нужны для того, чтобы физику изучить. Но лишних десяти лет ни у кого нет.

    Отсюда и кривые программы, использующиеся для расчётов. Цена ошибок в таком одноразовом коде ниже, чем затраты на исправление ошибок. Утекает память? Ну и что, когда программа закончилась, операционная система её всё равно освободит. Много однотипного кода? Это вообще не ошибка. Неинициализированная переменная? Если на результат это не влияет, кого это волнует? С таким же успехом можно критиковать физиков за плохой почерк.

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

    При необходимости можно отдать кривую одноразовую программу расчёта, которую физик написал, как умел, на Фортране, настоящему программисту и он сделает аналог на современном языке, который не стыдно будет продавать и поддерживать.
  • +3
    Анекдот вспомнился.
    2100-й год, генетический программист разбирает цепочку ДНК, и натыкается на комментарий:
    A5E2C1… // не забыть выпилить отсюда этот кусок кода нахрен (Архангел Михаил)
  • 0
    Если я правильно понял, одно из опасений автора — то, что серьезный физически/медицинский/химический [...] инструментарий пишут специально (не)обученные физики/медики/химики.

    Есть у меня подозрение, что, например, систему моделирования белков пишут не врачи, подучившие плюсы по третьему изданию Страуструпа, а специально обученные разработчики — которые могут вообще не иметь медицинского образования, зато умеют читать грамотно составленное тем же медиком ФС, в котором нет медицинской теории и предположений, зато есть четкие алгоритмы бизнес-уровня и формулы, расчет которых нужно реализовать программно.
    • 0
      К сожалению, в таком варианте, часто дела не лучше. Мы имеем "Заземлённые указатели". :)
    • +1
      Основная рассчетная часть @folding взята из GROMACS, который таки написали обычные физики.
      • 0
        Таки GROMACS одна из самых лучших и быстрых расчетных программ. И написана хорошо.
  • 0
    Если я правильно понял, одно из опасений автора — то, что серьезный физически/медицинский/химический [...] инструментарий пишут специально (не)обученные физики/медики/химики.

    Есть у меня подозрение, что, например, систему моделирования белков пишут не врачи, подучившие плюсы по третьему изданию Страуструпа, а специально обученные разработчики — которые могут вообще не иметь медицинского образования, зато умеют читать грамотно составленное тем же медиком ФС, в котором нет медицинской теории и предположений, зато есть четкие алгоритмы бизнес-уровня и формулы, расчет которых нужно реализовать программно.
  • +2
    Гениально! Редкий случай, когда я читал на одном дыхании и думал, вот это да, действительно круто написано, чтобы на это сказали ребята из PVS-Studio. И на тебе, и это реклама, только с длииинным-длинным приквелом.
    Растете!
  • +1
    Теперь я буду бояться всяких реакторов и коллайдеров :)
  • +1
    Он всю жизнь программировал на Фортране. Начинал еще с перфокарт. Я не виню его за то, что не учит C++… Никакого ООП в проекте под миллион строк кода, никакого статического анализа.  В том то и дело, что фортран превосходит С/C++  в области научных программ — нормальный компилятор фортрана сам массивы нолями инициализирует и не придется как в Си следить за указателями и совместимостью типов. Сам по себе фортран в научных вычислениях (из-за своей архитектуры) позволяет делать меньше ошибок. А ООП есть и в нем, если все таки оно действительно нужно в проекте.

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

    А ошибки можно и в  Excel  сделать, вспомним известную статью по экономике. Все по итогу упирается в мотивацию.
    • +3
      В вашем комментарии вижу домыслы и странную логику.
      нормальный компилятор фортрана сам массивы нолями инициализирует

      Укажите, пожалуйста, какой компилятор вы называете «нормальным»? Даже лёгкое гугление позволяет найти вот это: software.intel.com/en-us/forums/topic/367699, где особое внимание предлагаю обратить на фразу
      Arrays as well as scalar variables, whether allocated on the heap or the stack, have to be initialized before they are used.

      В компиляторе gcc, который мне симпатичнее по многим причинам, чем ifort, есть опция -finit-local-zero, которая инициализирует локальные переменные в функции нулями. Но ее наличие еще надо знать, ее надо включить, а кроме того она, как уже было указано, действует не на все, а только на локальные переменные. В любом случае, такая нетривиальная логика при решении настолько простого вопроса наводит на размышления. И подобные ситуации в Фортране случаются сплошь и рядом.

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

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

      Я нигде не утверждал, что C или C++ удобнее при математических расчетах, чем Фортран. Однако непродуманность основных концепций, которая встречается в Фортране (по сравнению с C и C++), у меня лично вызывает тоску. Навскидку, чтобы не быть голословным, предлагаю ответить на два вопроса:

      1. Каким способом в Фортране прочитать из файла строковую константу, оканчивающуюся тремя знаками пробела и не потерять их в конце (я искренне не смог найти способов работы с null terminated strings, возможно я плохо искал)
      2. Каким кроссплатформенным и кросскомпиляторным способом в языке, заметим, предназначенном для платформо-независимого математического программирования (то есть обязанном иметь универсальную стандартную библиотеку) создать директорию?

      Оба примера показывают, что таки да, в ряде случаев разработчику математического ПО на Фортране приходится решать совсем не математические проблемы. В отличие от C++, где всё это аккуратно решено за него. Но это — далеко не главные проблемы Фортрана. Дальше — больше;

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


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

      Рассмотрим, к примеру, хорошее решение поставленной проблемы с обнулением переменных. Прекрасно эта проблема решена в языках Java и C#. Там компилятор члены классов обнуляет автоматом, а локальную переменную разработчик обязан инициализировать сам. Это снимает проблему. Хочешь ноль? Пиши int i = 0;. Написал int i; и затем прочел из него значение — получаешь ошибку компиляции. При таком подходе проблем не будет никогда. Другой пример — не менее важный — отлов исключений в Java. Если функция кидает исключение, являющееся результатом нештатной ситуации — разработчик обязан либо поймать его, либо явно указать, что прокидывает его дальше. Да-да, я о том самом «throws». Не хочешь лишний раз думать и писать «лишний» код — иди, пиши скрипты. Большая программа — не игрушка.

      Я не знаю, как проблема инициализации переменных решена в питоне. Возможно, там тоже использование неинициализированной переменной — ошибка, хотя сильно в этом сомневаюсь, так как питон проектировался из расчета на лаконичность кода. И я даже готов смириться со стандартным поведением Фортрана, при котором переменные с именем «irrational» и «norm» имеют тип INTEGER, а переменная с именем «string1» — тип REAL, причем компилятор ни сном ни духом не даст понять, что вы забыли их объявить, если не напишите специально implicit none. Но тот факт, что переменные в функцию по умолчанию передаются по ссылке и могут в ней быть изменены, если явно возле каждой не писать intent(in) означает, что использовать этот язык для программирования крупных проектов — недальновидно. Вы будете искать ошибку в функции foo, а она будет в функции bar, которая «нечаянно» изменила переданную ей в параметре переменную. Это поведение языка было осмысленно, когда при программировании считали каждый такт и байт. Сейчас оно — преступно.

      Эти примеры (кстати, в современном Фортране вообще есть exceptions?) ясно показывают, что человек, в равной степени хорошо владеющий Фортраном и, скажем, C# при наличии смертельной ответственности за качество того, что он кодирует, выберет C#. Даже если писать в нем без использования ООП. Просто из-за прозрачности поведения компилятора и стандартов языка.

      Разумеется, и у Фортрана есть свои достоинства. К примеру, то, что на нем пишет половина физмат-сообщества и уже написала на нем кучу вспомогательного математического кода, который зачастую поможет вам решить проблемы. Есть хорошие математические библиотеки и т. д… Иными словами, Фортран популярен. Но давайте, положа руку на сердце, подумаем — а такое ли уж это достоинство? Мне приходит на ум аналогичный пример очень популярной операционной системы, созданной группой программистов из города Сиэттла, которая доминирует из-за того, что под нее написано больше всего пользовательских приложений. Вы уверены, что она действительно самая лучшая и удобная?..

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


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

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

          Поэтому чем меньше язык дает свободы разработчику среднего уровня (не знающему язык досконально) — тем больше шансов, что ограничения языка уберегут разработчика от проблем. Пример: в Java строки надо проверять так — s2.equals(s1). А в C# и C++ — так: s2 == s1. Для профессионала это уже не проблема (хотя не знаю ни одного джависта, которого бы эта «особенность» не бесила), а вот разработчик более слабый нет-нет да и споткнётся об это. Потому что в Java выражение s2 == s1 успешно компилируется, но неверно работает. Вот это — и есть главная беда в данном случае.

          Я питон не знаю. Спросите себя, насколько часто ваш успешно скомпилированный и успешно запущенный код делает не то, что вы от него ждали. Именно это и есть главный критерий.
          • +1
            Питон для математика — это Sage. И еще куча мелких модулей на все случаи жизни. Питон нужен просто как удобный язык для того, чтобы связывать все вместе. Это даже не дубина, а клей для дубины. Поэтому заморачиваться типами должен не математик, а разработчик модуля.

            Строки — больное место везде. В Джаве они еще реализованы более-менее удачно. Оператор == проверяет ссылки, равно как и для любого другого объекта любого класса, а для проверки содержимого строк есть специальный метод. Можно один раз запомнить и потом не спотыкаться. Похоже сделано и в Objective C, не помню, чтобы у кого-то были с этим серьезные проблемы.

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

            В плюсах строки — это вообще все и ничто одновременно. Десяток разных реализаций я точно видел. Далеко не все перегружают оператор ==, но стандартные, например, таки да. Но это, опять же, порождает класс проблем. Потому что null-terminated строчки со стандартной библиотекой Си никто тоже не отменял, а головы у программистов не сменные. Постоянно кто-то пытается складывать одно с другим в неправильном порядке, прибавлять чар к литералу или гонять строчки туда-сюда через c_str там, где это вовсе не нужно. К вопросу о строгости, компилятор это все жует не отрыгивая. Спасает только CppCheck.

            Питон такие проблемы обходит просто. Он их не порождает. Я не могу иметь неинициализированную переменную, потому что не могу декларировать переменную без инициализации. Строки — отдельный примитив языка, не объект как в Джаве и не список, как в Эрланге, поэтому они вполне могут сравниваться по ==. Переполнить массив невозможно, потому что вместо массивов списки. Сломать счетчик в цикле — потому что цикл проходит по неизменяемому ленивому ренджу. И так далее, и тому подобное.
            • 0
              а головы у программистов не сменные

              Эх, а как бы хорошо было иногда… А если серьёзно, то вы очень здорово сформулировали проблему обратной совместимости между C++ и C. Я бы сказал еще грубее: главная проблема этой совместимости в том, что она есть, а парадигмы — разные.

              К остальному, что вы сказали, мне добавить почти нечего. Спасибо за интересные факты. Уточню лишь, что под строками в C++ я имел в виду std::string, который находится в стандартной библиотеке и, по факту, является частью языка.

              Но одна вещь в вашем рассуждении показалась мне всё же непоследовательной:

              Строки — больное место везде.

              Вот тут полностью согласен, но кое-что добавлю: строки — проверка объектно-ориентированного языка «на вшивость», то есть проверка того, насколько в нём легко (и можно ли вообще) создавать производительные, удобные объекты, с которыми бы было легко и приятно работать. Из перечисленных языков проверку целиком выдержал лишь C++. Там строка реализована средствами языка. ее можно присваивать, строки можно соединять естественным способом (переопределен +), их можно передавать как по ссылке, так и по значению (копировать). Всё удобно, всё на месте. И работает достаточно быстро. В Java нет переопределения операторов, а значит удобной инкапсуляции тоже нет. Разработчики, отказавшись от переопределения операторов «из-за нетривиальности» должны бы были пойти дальше и сделать s2.concat(s1) вместо s2 += s1, но, понимая, что это будет чудовищно неудобно, они слицемерили и переопределили в языке ровно один оператор для ровно одного класса — "+" для String. А переопределить == забыли или не смогли. Сравнение строк по указателю означает равенство их по значению, но только обратное утверждение неверно. И это надо было встроить в ==, чтобы не было проблем.

              В C# объекты-ссылки действительно обычно сравниваются по указателю. string не был сделан объектом-значением из соображений производительности и экономии. И поэтому было принято решение «сломать мозг» тем, кто очень сильно хочет влезть в детали языка, а не тем, кто просто хочет сравнить две строки. В отличие от Java. Так что C# гордо займет второе место. А Питон просто дисквалифицировал себя из соревнования.

              Строки — отдельный примитив языка, не объект как в Джаве и не список, как в Эрланге, поэтому они вполне могут сравниваться по ==.

              Вот это — как раз и означает лютый костыль, по сравнению с которым тот, что в Java и тот, что в C# — сущие пустяки. Возможно, я неправ, но самый красивый и честный подход — в C++. А почему никто не считает, что stdc++ — стандартная библиотека, которую должно считать частью языка, для меня загадка. Возможно, виноват Герберт Шилдт, не уделивший ей должного внимания в своей суперпопулярной книге…
    • +1
      И вообще я действительно жалею, что программисты рекомендуют ученым переходить с фортрана на плюсы, хотя выгодней бы изучить питон, который также как и фортран хорош для научных вычислений.


      Вот вам написали длинный ответ, а на практике всё намного проще. Нашей группе было всё равно на чем писать, лишь бы не на бреинфаке, и вычисления были быстрыми. К сожалению, на чистом питоне в среднем всё считалось раз в 200-300 медленнее. И всё. Не имеет никакого значения объявление переменных, заточенность под ООП и прочее.
      • 0
        Просто не умеете его готовить (это про питон).
  • 0
    А вот пример, когда к программированию подошли более ответственно: habrahabr.ru/company/pvs-studio/blog/192996/
  • 0
    1. Какой-то неумеха продолжает писать неправильные циклы

    Судя по комментариям в файле, а также по истории коммитов репозитория, этот неумеха — Генли Лам:
    www.linkedin.com/pub/henry-lam/5/448/848

    http://ihome.ust.hk/~kehlam/
    image

    Henry Lam
    Insitute for Systems Biology
    1441 North 34th St.
    Seattle, WA 98103 USA
    hlam@systemsbiology.org
    (орфография сохранена)

    Файл с найденными дефектами он написал ещё в июне 2006 года, но до сих пор его поддерживает: есть свежий коммит — от 15 июня 2013 года.

    Оказывается, даже выпускники Массачусетского технологического могу писать кривой код.
    Отучился в США: MIT, Стенфорд, а потом вернулся в родной Гонконг на должность ассистент-профессора в гонконгский университет науки и технологии.

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

Самое читаемое Разработка