1. C++ / Говнокод #27179

    0

    1. 001
    2. 002
    3. 003
    4. 004
    5. 005
    6. 006
    7. 007
    8. 008
    9. 009
    10. 010
    11. 011
    12. 012
    13. 013
    14. 014
    15. 015
    16. 016
    17. 017
    18. 018
    19. 019
    20. 020
    21. 021
    22. 022
    23. 023
    24. 024
    25. 025
    26. 026
    27. 027
    28. 028
    29. 029
    30. 030
    31. 031
    32. 032
    33. 033
    34. 034
    35. 035
    36. 036
    37. 037
    38. 038
    39. 039
    40. 040
    41. 041
    42. 042
    43. 043
    44. 044
    45. 045
    46. 046
    47. 047
    48. 048
    49. 049
    50. 050
    51. 051
    52. 052
    53. 053
    54. 054
    55. 055
    56. 056
    57. 057
    58. 058
    59. 059
    60. 060
    61. 061
    62. 062
    63. 063
    64. 064
    65. 065
    66. 066
    67. 067
    68. 068
    69. 069
    70. 070
    71. 071
    72. 072
    73. 073
    74. 074
    75. 075
    76. 076
    77. 077
    78. 078
    79. 079
    80. 080
    81. 081
    82. 082
    83. 083
    84. 084
    85. 085
    86. 086
    87. 087
    88. 088
    89. 089
    90. 090
    91. 091
    92. 092
    93. 093
    94. 094
    95. 095
    96. 096
    97. 097
    98. 098
    99. 099
    100. 100
    #include <iostream>
    #include <string>
    using namespace std;
    class Govnokod {
        bool _flag_dot;
        bool _flag_mant;
        int _index_mant;
        bool vetka1(int i, const string stroka) {
            for (int j = i++; j < stroka.length(); j++) {
                switch (stroka[j]) {
                case '.':  
                    if (_flag_dot) return false;
                    _flag_dot = true;
                    break;     
                case '0' ... '9': break; 
                default:
                    return false;
                    break; }}
            return true;}
        bool vetka2_dalshe(const string stroka) {
            for (int j = 1; j < stroka.length(); j++) {
                switch (stroka[j]) {
                case '0' ... '9': break;
                default:
                    return false;
                    break; }} 
            return true; }
        bool vetka2(const string stroka) {
            switch (stroka[0]) {
            case '+':
            case '-':
                if (stroka.length() < 2) return false;  
                return vetka2_dalshe(stroka);
                break;   
            case '0' ... '9':
                return vetka2_dalshe(stroka);
                break;
            default:
                return false;
                break; }}
        bool mantissa(const string stroka) {
           for (int j = 0; j < stroka.length(); j++) {
               switch (stroka[j]) {
               case 'e':
               case 'E':            
                   if ((_flag_mant) or (j == (stroka.length() - 1))) return false;
                   _flag_mant = true;
                   _index_mant = j;
                   break; }}
           return true; }
        bool Dalshe(int i, const string stroka) {
            _flag_dot = false;
            _flag_mant = false;
            if (not mantissa(stroka)) return false;
            else if (_flag_mant) {
                string sub1 = stroka.substr(0, _index_mant);
                string sub2 = stroka.substr(_index_mant+1);
                return (vetka1(i, sub1) and vetka2(sub2)); }
            else return vetka1(i, stroka); }
        bool proverka(const string stroka) {
            switch (stroka[1]) {
            case '0' ... '9': 
                return Dalshe(1, stroka); break;   
            default: return false; break; }}
        bool general_proverka(const string stroka) {
            if (stroka.length() == 0) return false;
            switch (stroka[0]) {
            case '-':
            case '+':
                if (stroka.length() > 1) return proverka(stroka);
                else return false;
                break;  
            case '0' ... '9': 
                return Dalshe(0, stroka); 
                break; 
            default: return false; break; }}
        string cut_incorrect_symbol(const string stroka) {
            int j, i;
            string buf;
            for (j = 0, i = 0; j < stroka.length(); j++) {
                switch (stroka[j]) {
                case '0' ... '9':
                case '-':
                case '+':
                case '.':
                case 'e':
                case 'E': 
                    buf.push_back(stroka[j]); 
                    break;
                default: i++; break; }}
        return buf; }
        public:
        long double opros(char s) {
            string argument;
            while (true) {
                cout << "Введите значение " << s << ": ";
                getline(cin, argument);
                if (argument.length() == 0) cout << "Вы не ввели значение!" << endl;
                else if (not general_proverka(cut_incorrect_symbol(argument))) cout << "Некорректное значение!" << endl;
                else return strtold(argument.c_str(), nullptr); }}};

    Модифицированная версия говнокода проверки строки на корректность соответствия символов типу long double: изначально вырезаются все левые символы. А вообще этот модуль "govno.h", я написал для основной проги для решения квадратного уравнения.

    Запостил: Westnik_Govnokoda, 25 Декабря 2020

    Комментарии (11) RSS

    • 90 строка лишняя, как и переменная i в методе cut_incorrect_symbol() - забыл убрать также.
      Ответить
    • Имена плохие. С ними не то, что дать программу другу с просьбой посмотреть ошибку, с ними автор кода сам не разберётся в программе через 2 недели, я гарантирую это.

      > opros
      > general_proverka
      Смесь языков - плохой стиль. Лучше лишнее время провести со словарём и подобрать адекватное имя.

      > Dalshe
      > proverka
      Разный стиль именования вроде бы однотипных сущностей. Плохой стиль.

      > vetka1
      > vetka2_dalshe
      > vetka2
      Питушня, которая ничего не говорит читателю кода. Это уже не просто визуально плохой стиль как Dalshe и proverka, а
      1. долгий пердолинг читателя кода в попытках понять, что он делает
      2. шанс словить баг из-за того, что в голову не укладывается код
      Так именовать функции можно только в том случае, если программирование в вузе ведёт какой-то козёл, и хочется заставить его страдать при проверке этого перед тем, как свалишь в другой университет.

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

      Что-то такое:
      // noise cancellation using discrete Fourier transform: Khrenovitch - Filtering pituxes, Moscovia, 1251, p. 866.
      bool /* error */ dft_filter (std::vector<double>&);
      // converting a passed signal to its spectre
      bool /* error */ dft_filter_step1 (std::vector<double>&);
      // filter the signal spectre
      bool /* error */ dft_filter_step2 (std::vector<double>&);
      // revovering the signal from its filtered spectre
      bool /* error */ dft_filter_step3 (std::vector<double>&);


      Хотя, это только для примера. Алгоритм из примера и многие другие всё равно можно переписать через функции с понятными именами без номеров!
      Ответить
      • Помнится жесть была, когда кто-то из сокурсников(ниц) просил подсказать почему не работает код в Делфи, а там весь код выравнен просто по ширине, а не по уровню вложенности begin/end, и идентификаторы все на транслите. Ну и само собой методы вида button7_clicked, даже в лабе с расчетом матриц, Карл! Ведь не лень было edit для матриц 5х5 и 6х6 делать.
        Ответить
        • Хуже - если это смышлёный сокурсник(ца). Этот человек ещё достаточно умён, чтобы ещё мочь переваривать говно, которое он сам написал для относительно простых проектов, но ещё недостаточно умён, чтобы понять, что ему(ей) придётся потом пердолиться со своим кодом день и ночь. Такой человек - угроза для общества. Если человек достаточно умён, он(она) может довольно долго поддерживать свой код, и без ревью никто не будет знать, что при увольнении такого программиста работа всей конторы остановится.

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

      Такое разделение
      1. Упрощает написание программы. Каждая новая функция строится на основе простых предыдущих и становится синонимом новой комплексной операции.
      2. Упрощает чтение программы. Вместо того, чтобы читать код про комбинацию f(x) = 10*x + 4 и g(x) = -x + 1/2, читатель читает код про комбинацию f(x) = kx и g(x) = b.Первая пара имеют какой-то свой конкретный смысл, который нужен только для этой программы, а вторая пара абстрактна. С более общими и стандартными понятиями читатель сталкивается чаще и лучше их понимает. f(x) = kx и g(x) = b проще в усвоении, они более предсказуемые.
      3. Упрощает переиспользование кода и дописывание программы через долгое время простоя. Новый программист (или старый через полгода) имеет дело с набором функций, назначение и правила пользования которыми ясны.

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

      Учиться правильной декомпозиции надо сразу и применять везде, даже в самых простых проектах, где "всё как бы очевидно, зачем стараться". Потому, что потом привычка писать говнокод с говнодекомпозицией задачи перетекает в большие проекты.
      В итоге код человека, который не научился писать его аккуратно и логично, очень быстро деградирует в многосвязный непонятный клубок функций, классов и общего состояния, и для его поддержки требуется всё больше и больше ментальных усилий.
      Ответить
    • Предлагаю альтернативную декомпозицию задачи для примера: https://ideone.com/FON9Ku

      В ней есть свои преимущества и недостатки.

      У нас парсится число вида "+000.000E+000". Соответственно, можно заметить три повторяющихся куска про числа, парсинг знака, парсинг буквы E и точки, необязательность некоторых частей. При парсинге куска числа мы должны понять, удалось ли распарсить его и сколько символов занимает распарсенное значение.

      Соответственно, для строки str будем иметь текущую позицию pos в ней, и натравлять функции, которые будут начинать с pos и проверять некоторые свойства строки, двигать pos и возвращать, распарсено или нет.
      1. Надо создать парсер точки. У нас есть кроме точки ещё вариативные + и -, E и e, а также 0..9. Соответственно, нам понадобится парсер символа из пользовательского диапазона. Для цифр есть isdigit в ctype.h, поэтому можно реализовать парсинг цифры либо как парсинг любого символа из набора 01234566789, либо через isdigit.
      создали match_digit для парсинга цифры, match_symbol - для парсинга пользовательского символа.

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

      3. Число - набор цифр. На основе функции парсинга цифры делаем парсинг числа

      4. Думаем над парсингом "000.000", "000.", ".000", "000" и непарсингом ".". Можно просто запомнить pos и пройти по строке, начиная с pos, парсерами из набора наборов - число+точка+число, число+точка, точка+число - какой набор первый пройдёт, либо false. Это самый простой и надёжный способ.
      Однако ради производительности я стараюсь дважды не проходить по строке таким наивным образом. Поэтому я комбинирую парсер точки и числа в парсер дробной части match_frac и прогоняю последовательно через парсер числа (если что, откатываюсь к старому pos) и через парсер дробной части. Таким образом, "000.000", ".000", "000" у меня матчатся. Для "000." надо проверить отдельно одинокую точку, что я и делаю.
      Ответить
      • 5. Все элементарные парсеры обладали свойствами
        а. если распарсилось, то pos указывает на следующий нераспарсенный символ
        б. если не распарсилось, то pos указывает чёрт пойми куда
        в. если дошли до конца строки, вернули false и не вылезли за границу массива
        Это потенциально полезные свойства. Если в новых комплексных парсерах следим за его выполнением, то все парсеры им обладают, код унифицирован - его легче писать и проверять, а свойство "в" можно явно проверять только в элементарных парсерах, в следующих оно автоматически выполняетя.

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

        6. Самый главный парсер оборачиваем в функцию, которая принимает строку.

        Итого,
        1. у нас есть парсеры, из которых можно легко кобенировать новые парсеры
        2. операции идут от абстрактных, которые пригодятся в других задачах, к более конкретным, которые являются комбинацией абстрактных
        3. каждый маленький парсер можно понять (относительно понятное имя) и проверить отдельно
        4. код написан по законам своего диалекта
        5. в прошлой версии https://ideone.com/2mpH8O не работал для числа "0.0" из-за того, что match_digits раньше не откатывалась назад, когда читала не-цифру, и когда match_digits стала выполнять наши требования, всё заработало
        6. вероятно, можно вынести `match_*` в класс parser, который агрегирует parser_state или приватно от него наследуется.
        Ответить
        • Какой скилл )))
          Будешь делать учебник по программированию?
          Ответить
          • Оставим учебник по программированию как упражнение для вореционного кобенатора дяденьки Пи.
            Когда он вернётся, мы подготовим ему материалы не только по матану, но и основам проектирования.
            Ответить
        • Но всё же не могу не заметить, что задача правильного парсинга плавающего питуха несколько сложнее данного примера, см. https://en.cppreference.com/w/cpp/string/byte/strtof, «The valid floating-point value can be one of the following».
          Ответить
          • Если выкинуть "any other expression that may be accepted by the currently installed C locale" и всю фигню, связанную с локалями, можно расширить пример.
            Хотя, действительно такой парсинг не заменит стандартное решение, где уже обо всём позаботились.

            Идея с комбинациями парсеров и откатами при неудаче тут работает.
            1. Каждому пункту их списков сопоставим функцию, которая парсит этот кусок.
            2. Каждому пункту внутренних списков с optional поставим в соответствие парсер с самооткатом (как мой maybe_match_sign). Каждому внутренних списков без optional - обычный парсер.
            3. Каждый пункт внешнего списка - запуск парсеров из подсписков &&.
            4. Парсер числа - запуск парсеров для пунктов внешнего списка последовательно (каждый - с начала строки), до первого совпадения.

            Моя питушня расширяема, однако "nonempty sequence of decimal digits optionally containing decimal-point character (as determined by the current C locale)" гораздо легче реализуется методом автора с флагами, чем мой парсинг кобенаций "000.000", ".000", "000.", "000" с попыткой натянуть иерархию на мантиссу. Можно переписать матчинг мантиссы со знаком через это:
            bool match_mantissa(parser_state& s) {
              maybe_match_sign(s);
              bool matched_digits = false, matched_point = false;
            
              // match nonempty sequence of decimal digits optionally containing decimal-point character
              while (!s.end()) {
                if (isdigit(s.str[s.pos])) {
                  matched_digits = true;
                } else {
                  if (matched_point || s.str[s.pos] != '.') return matched_digits;
                  matched_point = true;
                }
                s.pos ++;
              }
            
              return matched_digits;
            }
            Ответить

    Добавить комментарий