Как Вы боретесь с ошибками?

IT IT
Здравствуйте, _Mihail, Вы писали:

Несколько практических советов от настоящих индейцев.

1. Настоящий индеец прежде всего заходит в меню Debug и в диалоге Exceptions включает галку Thrown на CLR Exceptions для managed языков. Это позволяет сэкономить не просто хучу, а туеву хучу времени при поиске ошибок. Отсюда следствие — настоящие индейцы не используют логику на исключениях, иначе весь кайф пропадает.

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

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

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

5. Настоящий индеец думает 10 раз прежде чем объявить статическую переменную. Статический контекст самый злобный источник багов и кривого дизайна. Даже если не будет багов, то кривизну дизайна в дальнейшем придётся выкривлять с помощью линз с обратной кривизной. Если же индеец всё же решилися на использование статического контекста, то он возвращается к началу данного пункта ещё раз. В принципе, статические переменные можно использовать для кеширования некоторых данных при соблюдении правил гигиены в многопоточных приложениях. В остальных случаях как правило стоит серьёзно задуматься.

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

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

8. Настоящий индеец знает, что только что написанный код обязательно будет меняться либо им самим, либо кем-то другим, может быть через 5 минут, может быть через 5 лет. В трудно модифицируемом коде сложно исправлять существующие баги и не вностить новые. Поэтому, основной критерий качества кода для настоящего индейца — это готовность кода к модификациям и способность после этого оставаться готовым к следующим модификациям.
FDSC
FDSC Можно парочку дерзких вопросов?
29.03.2007 04:28
Здравствуйте, IT, Вы писали:

IT>Несколько практических советов от настоящих индейцев.




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


Ммм. А если их сложно напечатать, что делать?
Например, 4-х мерная матрица 100х100х100х100 (помню, я так и не написал ту программу из-за того, что не смог нормально проанализировать её содержание)
Или, скажем, граф с представлением в виде списков следования? Это ж замучиться распечатывать! (я форму специальную делал)

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


Хм. А вот что делать, если мне нужно реализовать процедуру умножения транспонированных матриц (очень просто, неправда ли?).
Варианты реализации:

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

2. Записать почти одинаковые алгоритмы для всех 4-х вариантов (т.е. AB, AtB, ABt, AtBt)
Минус: 4 раза копируется один и тот же метод

3. Записать метод с передачей в него флагов транспонированности, но тогда там тоже будет небольшой copy/paste
Минус: пользователь запутывается с флагами и становится тяжело использовать метод или необходимо написать 4 доп. метода, вызывающих этот для разных вариантов перемножений

4. Обращаться к матрице через свойство, что бы можно было не транспонировать матрицу физически
Минусы: * придётся возится с передачей указателей на соотв. методы взятия элемента матрицы, что неудобно
* небольшое (но чувствительное) снижение производительности в связи с дополнительными вызовами методов

5. Я могу писать перемножение прямо в методе (кстати, так и делаю, хоть дальше ругаюсь на пункт 4)
Минус: постоянные повторения одного и того же кода

Какой из этих вариантов предпочтительней?

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


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


Вот с этого начнём, точнее закончим. Не согласен

Где прятаться багам, если методы до отупения просты? Представьте себе, у вас все методы из 3-5 строк. Ну 10 максимум, обычно.
При этом есть одно условие: название вызываемого метода говорит о том, что этот метод делает, а этот метод делает только то, что сказано в названии (это, кстати, рекомендация Макконнелла). Тогда получаем кучу вот этих самых методов, которые просто и в которых не где прятаться багам, так как, фактически, такой код представляет собой обычный псевдокод.
Я почему это говорю, я не так давно пробовал (на C++) писать исключительно мелкими методами, как сказал, и скорость разработки у меня повысилась приблизительно в 1,5 раза (с отладкой). При этом большое количество багов из разряда логических ошибок перекочевало в разряд "забыл вызвать метод", "при кодировании использовал неправильный алгоритм расчётов". Кстати, в последнем случае, в мелких методах гораздо лучше видно несоответствие кода используемому алгоритму и несоответствие алгоритма решаемой задачи.

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


О! В январе так делал. Правда на Delphi, а не на C#. Сначала я написал бо-о-о-ольшую процедуру (200-300 строк). Её предназначение было — распознавание зашумлённых шестнадцатиричных чисел, написанных от руки на неровной поверхности. Дальше у меня появились предложения по улучшению... я стал писать вложенные процедуры, по 50-70 строк. Дальше это всё отказалось стабильно и хорошо работать . В итоге вся эта первоначальная процедура с вложенными функциями занимала почти 1000 строк.

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


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

Я переписал всё через несколько классов, используя маленькие логичные как можно более простые блоки (и как можно более простые классы). Заняло у меня это один день, всё заработало и даже довольно прилично смотрится.

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

Осенью я писал расчёт деформаций шпинделя (C++) и пока не начал разбивать неожиданно разросшийся класс на два, не нашёл правильного решения. Плюс, у меня там был как раз такой метод, какой вы советуете: генерация матрицы жёсткости. В этом методе было наибольшее число ошибок, он был труден для понимания и т.п. Разбей я его на несколько методов — всё было бы хорошо.
Может быть я вас неправильно понял?
Вот этот уродец (он и сейчас такой ):

// Вычисление формы деформации конструкции и реакций опор
void Spindle::CalcShifts()
{
        // Вычислить длины массива параметров опор и количество столбцов в матрице жёсткости
    int SC = Iteration + 2;
    int Lk = (SC + 1) * 2;
    K      = new double[Lk * (Lk + 1)];

    double * Kf = new double[Lk * (Lk + 1)];

    for (int i = 0; i < Lk * (Lk + 1); K[i++] = 0.0);

    // Создать систему разрешающих уравнений
    // Записать коэффициенты уравнений без учёта кинематических ограничений
    for (i = 0; i < SC; i++)
    {
        Bars[i]->SetK(K, Lk);
    }

    // Записать правые части
    *(K + 0        + Lk) = F;
    *(K + (Lk + 1) + Lk) = M;

    // Скопировать матрицу жесткостей без кинематических ограничений для дальнейших вычислений рекаций опор
    for (i = 0; i <  Lk * (Lk + 1); i++)
    {
        Kf[i] = K[i];
    }

    // Учесть кинематические ограничения первых двух опор обнулением столбцов, так как это упростит расчёт
    for (i = 0; i < Lk; i++)
    {
        *(K + (Lk + 1) * i + (2)     ) = 0.0;
        *(K + (Lk + 1) * i + (Lk - 2)) = 0.0;
    }

    // Учесть кинематические ограничения всех опор
    for (int j = 0; j < SC; j++)
    {
        for(int i = 0; i < Lk; i++)
        {
            *(K + (Lk + 1) * (j + 1)*2 + i) = 0.0;        // j + 1, так как первый узел ни на что не опирается; *2 - так как узлу соответсвует две строчки (перемещение и поворот)
        }
        *(K + (Lk + 1) * (j + 1)*2 + Lk) = Sp[j + this->maxSupportCount * 2];    // Координата кинематического ограничения по y
        *(K + (Lk + 1) * (j + 1)*2 + (j + 1)*2) = 1.0;                            // Единица на главной диагонали

//        ::MessageBox(0, itoa(Sp[j + this->maxSupportCount * 2]*1000000000, new char[11], 10), "EE", 0);
    }

    // Решить систему
    double * Y = NULL;

    // Отладка
    // #define DBG__
    #ifdef DBG__
        static int Z = 0;
        Z++;
        HANDLE File;
        int brum = 0;
        if (Z == 2)
        {
        File = ::CreateFileA("G:\\debug.tmp", GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
        ::WriteFile(File, K, Lk*(Lk + 1)*sizeof(double), (LPDWORD)&brum, 0);
        }
    #endif

    MGauss(K, Y, Lk);

    // Отладка
    #ifdef DBG__
        if (Z == 2)
        {
        ::WriteFile(File, Y, Lk*sizeof(double), (LPDWORD)&brum, 0);
        ::CloseHandle(File);
        }
    #endif
    #undef DBG__

    // Установить эти значения в стержни
    for (i = 0; i < SC; i++)
    {
        Bars[i]->SetY(Y);
    }

    // Вычислить реакции опор (только силы, моменты равны нулю)
    for (i = 0, j = 2; j < Lk; i++, j += 2)
    {
        // Обнуление обязательно, т.к. на каждой итерации расчёта идёт запись в одну и ту же строку
        this->Sp[maxSupportCount*3 + i] = 0.0;

        for (int k = 0; k < Lk; k++)
        {
            this->Sp[maxSupportCount*3 + i] += Y[k] * Kf[k + (Lk + 1)*j];
        }
    }

    delete [] Kf;
    delete [] K;
    delete [] Y;
}





bkat
bkat
29.03.2007 05:20
Здравствуйте, FDSC, Вы писали:


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


FDS>Вот с этого начнём, точнее закончим. Не согласен


FDS>Где прятаться багам, если методы до отупения просты? Представьте себе, у вас все методы из 3-5 строк. Ну 10 максимум, обычно.


У тебя никогда не было такого, что каждый отдельный метод на 3-5 строк делает
вроде все правильно, а вот в совокупности получается фигня?
FDSC
FDSC
29.03.2007 06:19
Здравствуйте, bkat, Вы писали:

B>Здравствуйте, FDSC, Вы писали:



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


FDS>>Вот с этого начнём, точнее закончим. Не согласен


FDS>>Где прятаться багам, если методы до отупения просты? Представьте себе, у вас все методы из 3-5 строк. Ну 10 максимум, обычно.


B>У тебя никогда не было такого, что каждый отдельный метод на 3-5 строк делает

B>вроде все правильно, а вот в совокупности получается фигня?

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

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

Что касается большого количества "странствующих" по методам параметров (не помню, как они по человечески называются), то Delphi и Nemerle позволяют писать вложенные функции и кол-во этих параметров уменьшается до 0. На C++/C# всё, конечно, хуже.
aka50
aka50
29.03.2007 05:33
Здравствуйте, FDSC, Вы писали:

FDS>Ммм. А если их сложно напечатать, что делать?

FDS>Например, 4-х мерная матрица 100х100х100х100 (помню, я так и не написал ту программу из-за того, что не смог нормально проанализировать её содержание)
FDS>Или, скажем, граф с представлением в виде списков следования? Это ж замучиться распечатывать! (я форму специальную делал)

А что мешает в этом случае делать дамп в файл? Тесты даже могут сравнивать его с заранее правильным файлом.

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


FDS>Хм. А вот что делать, если мне нужно реализовать процедуру умножения транспонированных матриц (очень просто, неправда ли?).

FDS>Варианты реализации:

[skip]

FDS>Какой из этих вариантов предпочтительней?

FDS>И не будет ли после того, как написал два раза одинаковый код, мучительно вспоминать, а где же я его ещё писал-то?

inline спасет... я так думаю в купе с шаблонами, если это С++.

FDS>О! В январе так делал. Правда на Delphi, а не на C#. Сначала я написал бо-о-о-ольшую процедуру (200-300 строк). Её предназначение было — распознавание зашумлённых шестнадцатиричных чисел, написанных от руки на неровной поверхности. Дальше у меня появились предложения по улучшению... я стал писать вложенные процедуры, по 50-70 строк. Дальше это всё отказалось стабильно и хорошо работать . В итоге вся эта первоначальная процедура с вложенными функциями занимала почти 1000 строк.

Рефакторить всегда сложнее...
FDSC
FDSC
29.03.2007 06:06
Здравствуйте, aka50, Вы писали:

A>Здравствуйте, FDSC, Вы писали:


FDS>>Ммм. А если их сложно напечатать, что делать?

FDS>>Например, 4-х мерная матрица 100х100х100х100 (помню, я так и не написал ту программу из-за того, что не смог нормально проанализировать её содержание)
FDS>>Или, скажем, граф с представлением в виде списков следования? Это ж замучиться распечатывать! (я форму специальную делал)

A>А что мешает в этом случае делать дамп в файл? Тесты даже могут сравнивать его с заранее правильным файлом.


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


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


FDS>>Хм. А вот что делать, если мне нужно реализовать процедуру умножения транспонированных матриц (очень просто, неправда ли?).

FDS>>Варианты реализации:

A>[skip]


FDS>>Какой из этих вариантов предпочтительней?

FDS>>И не будет ли после того, как написал два раза одинаковый код, мучительно вспоминать, а где же я его ещё писал-то?

A>inline спасет... я так думаю в купе с шаблонами, если это С++.


А шаблоны тут зачем?
Т.е. вы предлагаете вариант со свойствами и объявлением метода доступа к элементу как inline?
Пожалуй, сойдёт. Только придётся в метод передавать указатель на метод доступа: не слишком красиво.
Вообще, я уже что-то подобное в ФП обсуждал. Только немного с другой стороны. Насколько я помню, мне всё-таки предложили довольно хорошее и, в общем-то, очевидное решение

FDS>>О! В январе так делал. Правда на Delphi, а не на C#. Сначала я написал бо-о-о-ольшую процедуру (200-300 строк). Её предназначение было — распознавание зашумлённых шестнадцатиричных чисел, написанных от руки на неровной поверхности. Дальше у меня появились предложения по улучшению... я стал писать вложенные процедуры, по 50-70 строк. Дальше это всё отказалось стабильно и хорошо работать . В итоге вся эта первоначальная процедура с вложенными функциями занимала почти 1000 строк.

A>Рефакторить всегда сложнее...

А я про то же. Зачем писать крупные модули, если можно было сразу сделать мелкие? Правда, я очень торопился и постоянно менял алгоритм — это единственное, что является оправданием крупным модулям.
aka50
aka50
29.03.2007 06:39
Здравствуйте, FDSC, Вы писали:

FDS>Здравствуйте, aka50, Вы писали:


A>>Здравствуйте, FDSC, Вы писали:


FDS>>>Например, 4-х мерная матрица 100х100х100х100 (помню, я так и не написал ту программу из-за того, что не смог нормально проанализировать её содержание)

A>>А что мешает в этом случае делать дамп в файл? Тесты даже могут сравнивать его с заранее правильным файлом.
FDS>Мешает то, что никто не знает, какой он правильный. Для этого его сначала вручную посчитать надо — спасибо, не хочу.
[skip]
FDS>Т.е. речь идёт о том, что в файл информацию сохранять нет смысла, потому что легче сразу обрабатывать некоторую её часть по запросу тестировщика и выдавать в соотв. виде. Т.е. я просто забыл поесть и стал придираться к словам : мол, файлы не всегда спасают
Правильный — это для тестов, а файл — для удобства просмотра (ведь можно хоть после каждой итерации новый файл писать и потом
diff-ом его, если больше одной ячейки за итерацию меняется... Или наоброт один файл сделать и например FAR будет видеть что файл
изменился и перечитывать, а у тебя будет выглядеть как в реалтайме меняющаяся картина матрицы...

FDS>>>Какой из этих вариантов предпочтительней?

FDS>>>И не будет ли после того, как написал два раза одинаковый код, мучительно вспоминать, а где же я его ещё писал-то?

A>>inline спасет... я так думаю в купе с шаблонами, если это С++.

FDS>А шаблоны тут зачем?
FDS>3. Записать метод с передачей в него флагов транспонированности, но тогда там тоже будет небольшой copy/paste Минус: пользователь запутывается с флагами и становится тяжело использовать метод или необходимо написать 4 доп. метода, вызывающих этот для разных вариантов перемножений

Но вместо функции с параметрами делаем шаблон с параметрами и для разных параметров шаблон раскроется в соотвествующую
функцию http://www.boost.org/libs/mpl/doc/tutorial/dimensional-analysis.html
FDSC
FDSC
29.03.2007 06:50
Здравствуйте, aka50, Вы писали:

FDS>>Т.е. речь идёт о том, что в файл информацию сохранять нет смысла, потому что легче сразу обрабатывать некоторую её часть по запросу тестировщика и выдавать в соотв. виде. Т.е. я просто забыл поесть и стал придираться к словам : мол, файлы не всегда спасают

A>Правильный — это для тестов, а файл — для удобства просмотра (ведь можно хоть после каждой итерации новый файл писать и потом
A>diff-ом его, если больше одной ячейки за итерацию меняется... Или наоброт один файл сделать и например FAR будет видеть что файл
A>изменился и перечитывать, а у тебя будет выглядеть как в реалтайме меняющаяся картина матрицы...

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

FDS>>3. Записать метод с передачей в него флагов транспонированности, но тогда там тоже будет небольшой copy/paste Минус: пользователь запутывается с флагами и становится тяжело использовать метод или необходимо написать 4 доп. метода, вызывающих этот для разных вариантов перемножений


A>Но вместо функции с параметрами делаем шаблон с параметрами и для разных параметров шаблон раскроется в соотвествующую

A>функцию http://www.boost.org/libs/mpl/doc/tutorial/dimensional-analysis.html

Ужос! Хотя это не совсем то, что я думал, если я правильно понял.
aka50
aka50
29.03.2007 07:26
Здравствуйте, FDSC, Вы писали:

FDS>Здравствуйте, aka50, Вы писали:


FDS>Это не матрица, но всё равно.

FDS>Просто его потом ведь ещё замучеешься анализировать. Т.е. сделал diff, нашёл, какие ячейки изменились. Но тут же стал интересоваться некоторой другой информацией. А найти её можно только линейный поиском по файлу А в рантайме ячейки хранят ссылки, которые программе легко считать и вывести информацию.
Согласен, зависит от задачи... иногда проще целый GUI прикрутить, чтобы понять... я так в дипломе делал...
Andrei N.Sobchuck
Andrei N.Sobchuck
30.03.2007 08:09
Здравствуйте, FDSC, Вы писали:

A>>А что мешает в этом случае делать дамп в файл? Тесты даже могут сравнивать его с заранее правильным файлом.


FDS>Мешает то, что никто не знает, какой он правильный. Для этого его сначала вручную посчитать надо — спасибо, не хочу.


Тесты это способ фиксации собственных знаний. Если ты чего-то не знаеш, то тесты на такой случай, естественно, не напишешь.

Правильность алгоритма тестом тоже не докажеш. Но своё "понимание" алгоритма в тесте зафиксировать можно.
IT
IT
29.03.2007 06:23
Здравствуйте, FDSC, Вы писали:

FDS>Ммм. А если их сложно напечатать, что делать?

FDS>Например, 4-х мерная матрица 100х100х100х100 (помню, я так и не написал ту программу из-за того, что не смог нормально проанализировать её содержание)
FDS>Или, скажем, граф с представлением в виде списков следования? Это ж замучиться распечатывать! (я форму специальную делал)

И как ты в конце концов решил эту проблему?

FDS>Хм. А вот что делать, если мне нужно реализовать процедуру умножения транспонированных матриц (очень просто, неправда ли?).

FDS>Варианты реализации:
...
FDS>Какой из этих вариантов предпочтительней?

9. Настоящие индейцы руководствуются прежде всего здравым смыслом.

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


FDS>Вот с этого начнём, точнее закончим. Не согласен


FDS>Где прятаться багам, если методы до отупения просты? Представьте себе, у вас все методы из 3-5 строк. Ну 10 максимум, обычно.


Это было отражено во втором пункте.

FDS>При этом есть одно условие: название вызываемого метода говорит о том, что этот метод делает, а этот метод делает только то, что сказано в названии (это, кстати, рекомендация Макконнелла). Тогда получаем кучу вот этих самых методов, которые просто и в которых не где прятаться багам, так как, фактически, такой код представляет собой обычный псевдокод.


Просматривая реализацию ты будешь вынужден скакать по всему коду взад вперёд. Это не удобно. Если объём метода не вредит понимабельности и не грозит потенциальными багами, то искуственно разбивать его на отдельные методы не имеет смысла.

FDS>Я почему это говорю, я не так давно пробовал (на C++) писать исключительно мелкими методами, как сказал, и скорость разработки у меня повысилась приблизительно в 1,5 раза (с отладкой). При этом большое количество багов из разряда логических ошибок перекочевало в разряд "забыл вызвать метод", "при кодировании использовал неправильный алгоритм расчётов". Кстати, в последнем случае, в мелких методах гораздо лучше видно несоответствие кода используемому алгоритму и несоответствие алгоритма решаемой задачи.


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

FDS>Как вы это объясните?


Что именно? То что со временем твой метод превратился сначала в несколько методов, а потом в класс? Так вроде я именно об этом и говорю. Или нет?

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


Честно говоря, я так и не понял, ты споришь или соглашаешься.

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


И какой вывод из этого следует?

FDS>Может быть я вас неправильно понял?


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

FDS>Вот этот уродец (он и сейчас такой ):


А что получилось в результате?
... << RSDN@Home 1.2.0 alpha rev. 0>>
FDSC
FDSC
29.03.2007 06:43
Здравствуйте, IT, Вы писали:

FDS>>Или, скажем, граф с представлением в виде списков следования? Это ж замучиться распечатывать! (я форму специальную делал)


IT>И как ты в конце концов решил эту проблему?


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

IT>9. Настоящие индейцы руководствуются прежде всего здравым смыслом.




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


FDS>>Где прятаться багам, если методы до отупения просты? Представьте себе, у вас все методы из 3-5 строк. Ну 10 максимум, обычно.


IT>Это было отражено во втором пункте.


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

IT>Просматривая реализацию ты будешь вынужден скакать по всему коду взад вперёд. Это не удобно. Если объём метода не вредит понимабельности и не грозит потенциальными багами, то искуственно разбивать его на отдельные методы не имеет смысла.


Если группировать функции, то скачки можно минимизировать.
Дело в том, что у меня объём кода всегда грозит багами Поэтому и стал об этом писать. Т.е. у вас это не так?

FDS>>Я почему это говорю, я не так давно пробовал (на C++) писать исключительно мелкими методами, как сказал, и скорость разработки у меня повысилась приблизительно в 1,5 раза (с отладкой). При этом большое количество багов из разряда логических ошибок перекочевало в разряд "забыл вызвать метод", "при кодировании использовал неправильный алгоритм расчётов". Кстати, в последнем случае, в мелких методах гораздо лучше видно несоответствие кода используемому алгоритму и несоответствие алгоритма решаемой задачи.


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


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

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


IT>И какой вывод из этого следует?


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

FDS>>Может быть я вас неправильно понял?


IT>Возможно ты просто путаешь декомпозицию метода с повторным более углублённым его переосмыслением.


Нет, не путаю Если декомпозицию делать сразу, то и смысл быстрее выявляется и он лежит на поверхности. В этом весь и прикол. Что не нужно повторного более глубокого осмысления. Оно и так насатаёт

FDS>>Вот этот уродец (он и сейчас такой ):


IT>А что получилось в результате?


Сам код я менять не стал. Всё хорошо и работает, а больше он вряд ли понадобится. Т.е. он такой и есть, я в скобках написал. Но именно на эту функцию я убил больше всего (и большую часть) времени на отладке.
IT
IT
29.03.2007 07:20
Здравствуйте, FDSC, Вы писали:

IT>>И как ты в конце концов решил эту проблему?


FDS>Формой с методами обработки информации.


Т.е. написал для отладки отдельный гуй?

IT>>Это было отражено во втором пункте.


FDS>Вот я и не понял. Как же он хорошо отформатирован, если он большой?


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

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


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

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

FDS>Если группировать функции, то скачки можно минимизировать.

FDS>Дело в том, что у меня объём кода всегда грозит багами Поэтому и стал об этом писать. Т.е. у вас это не так?

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

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

IT>>И какой вывод из этого следует?


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


Не факт, что ты получил незаметную ошибку именно по-этому.

IT>>Возможно ты просто путаешь декомпозицию метода с повторным более углублённым его переосмыслением.


FDS>Нет, не путаю Если декомпозицию делать сразу, то и смысл быстрее выявляется и он лежит на поверхности. В этом весь и прикол. Что не нужно повторного более глубокого осмысления. Оно и так насатаёт


Убейте меня застрелите. Я не понимаю как декомпозиция метода позволяет мне глубже осмыслить алгоритм над которым я работаю
... << RSDN@Home 1.2.0 alpha rev. 0>>
Mirrorer
Mirrorer
30.03.2007 11:40
Здравствуйте, IT, Вы писали:

IT>Кстати, один из способов, позволяющих решить эту проблему, это локальные функции. К сожалению, в C# этого нет, и пока, как я понял не планируется.


Ну можно это будет лямбдами сэмулировать. Хотя для 3х уровней вложенности это ужастик получится, да.
"Если Вы отличаетесь от меня, то это ничуть мне не вредит — Вы делаете меня богаче". Экзюпери
FDSC
FDSC
30.03.2007 11:50
Здравствуйте, Mirrorer, Вы писали:

M>Здравствуйте, IT, Вы писали:


IT>>Кстати, один из способов, позволяющих решить эту проблему, это локальные функции. К сожалению, в C# этого нет, и пока, как я понял не планируется.


M>Ну можно это будет лямбдами сэмулировать. Хотя для 3х уровней вложенности это ужастик получится, да.


В C# такие лямбды, что уж лучше контекст передавать или прямо сразу доп. класс сделать. Не смотрятся там лямбды как повсеместная замена вложенных функций.
IT
IT
30.03.2007 02:00
Здравствуйте, Mirrorer, Вы писали:

IT>>Кстати, один из способов, позволяющих решить эту проблему, это локальные функции. К сожалению, в C# этого нет, и пока, как я понял не планируется.


M>Ну можно это будет лямбдами сэмулировать. Хотя для 3х уровней вложенности это ужастик получится, да.


На каждую такую функцию уморишься делегаты ообъявлять. Короче, лажа это. Без крайней необходимости никто этим пользоваться не будет.
... << RSDN@Home 1.2.0 alpha rev. 0>>
VladD2
VladD2
04.04.2007 01:23
Здравствуйте, IT, Вы писали:

IT>>>Кстати, один из способов, позволяющих решить эту проблему, это локальные функции. К сожалению, в C# этого нет, и пока, как я понял не планируется.


M>>Ну можно это будет лямбдами сэмулировать. Хотя для 3х уровней вложенности это ужастик получится, да.


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


Кстати, илюстрация к грусному. По причине завязанности алгоритма на некоторые классы Студии был вынужден писать класс-хелпер на C# 2.0. Но к лямбдам и т.п. как-то прям тянет. В итоге родилось вот такое чудо:
#region MatchBraces()

private AuthoringScope MatchBraces(ParseRequest request)
{
  if (!request.Sink.BraceMatching)
    return GetDefaultScope(request);

  // Steps: 
  // 1. Find token under text caret.
  // 2. Determine that it is a paired token.
  // 3. Determine paired token.
  // 4. Find paired token in the source file.
  // 5. Set info about paired tokens Sink and return it in AuthoringScope.

  #region Init vars

  ProjectInfo projectInfo = GetProjectInfo(request);

  if (projectInfo == null)
    return GetDefaultScope(request);

  NemerleSource source = projectInfo.GetSource(request.FileName);
  IVsTextColorState colorState = source.ColorState;
  Colorizer colorizer = source.GetColorizer();
  NemerleScanner scanner = (NemerleScanner)colorizer.Scanner;
  string lineText = source.GetLine(request.Line);
  scanner.SetSource(lineText, 0);

  #endregion

  // Steps: 1-3
  BracketFinder bracketFinder = new BracketFinder(source, request.Line + 1,
    request.Col + 1, scanner, colorState);

  // 4. Find paired token in the source file.
  ScanTokenInfo matchBraceInfo = bracketFinder.FindMatchBraceInfo();

  if (matchBraceInfo != null)
  {
    // 5. Set info about paired tokens Sink and return it in AuthoringScope.

    request.Sink.FoundMatchingBrace = true;

    // Fix a bug in MPF: Correct location of left token.
    // It need for correct navigation (to left side of token).
    Token mactTok = matchBraceInfo.Token;
    Location mactLoc = request.Reason == ParseReason.MatchBraces 
      && !BracketFinder.IsOpenToken(mactTok)
      ? mactTok.Location.FromEnd() : mactTok.Location;

    request.Sink.MatchPair( // Set tokens position info
      Convert(bracketFinder.StartBraceInfo.Token.Location),
      Convert(mactLoc), 0);

    return new NemerleAuthoringScope(
      projectInfo, request.Sink, request.FileName,
      new SourceTextManager(projectInfo.GetSource(request.FileName)));
  }

  return GetDefaultScope(request); // we don't fing paired token
}

#endregion

#region BracketFinder class

/// <summary>
/// Helper class which match paired token (brackets, brace, etc.)
/// </summary>
private class BracketFinder
{
  #region Fields
  
  public ScanTokenInfo StartBraceInfo;
  IVsTextColorState ColorState;
  NemerleSource Source;
  int StartLine;
  ScanLexer Lex;
  NemerleScanner Scanner;

  #endregion

  public BracketFinder(NemerleSource source, int startLine,
    int startCol, NemerleScanner scanner, IVsTextColorState colorState)
  {
    #region Init fields

    Scanner = scanner;
    Source = source;
    StartLine = startLine;
    Lex = scanner.GetLexer();
    Lex.SetFileName(source.GetFilePath());
    ColorState = colorState;
    
    #endregion

    #region 2. Determine that it is a paired token. 3. Determine paired token.
    
    // Get tokens of line under text carret into dynamic array.
    List<ScanTokenInfo> lineToks = GetLineTokens(startLine, true);
    // Find index of token which located under text carret.
    int index = FindIndex(lineToks,
      delegate(ScanTokenInfo x)
      { return x.Token.Location.Contains(startLine, startCol); });

    // If index is corret get corresponding token.
    ScanTokenInfo startBraceInfo = index < 0 ? null : lineToks[index];
    // Remember it, if token have paired token.
    if (IsPairedToken(startBraceInfo.Token))
      StartBraceInfo = startBraceInfo;
    else
    {
      // otherwise try get right-hand token...
      startBraceInfo = RightHand(lineToks, index);
      // Remember it, if token have paired token.
      if (IsPairedToken(startBraceInfo.Token))
        StartBraceInfo = startBraceInfo;
    } 

    #endregion
  }

  public ScanTokenInfo FindMatchBraceInfo()
  {
    if (StartBraceInfo == null)
      return null;

    // 4. Find paired token in the source file.

    Token tok = StartBraceInfo.Token;
    Predicate<Token> isStartBrace = GetMatchBracePredicate(tok);
    Predicate<Token> isEndBrace = GetMatchBracePredicate(GetPairedToken(tok));
    int nestedLevel = 1;

    foreach (ScanTokenInfo tokInfo in GetTokenIter())
    {
      if (isEndBrace(tokInfo.Token))
        nestedLevel--;
      else if (isStartBrace(tokInfo.Token))
        nestedLevel++;
      if (nestedLevel == 0)
        return tokInfo; // Match found!
    }

    return null; // Match not found.
  }

  #region GetTokenIter()

  /// <summary>
  /// Return iterator which allow iterate throw tokens of curent 
  /// source file. Iteration process start from token behind current 
  /// token and go forward or backward depending on type of start token.
  /// If start token is open token (for example, "(" or "{") iteration
  /// execute forward. If start token is close token (for example, ")" 
  /// or "]") iteration execute backward.
  /// </summary>
  private IEnumerable<ScanTokenInfo> GetTokenIter()
  {
    bool isScanForward = IsOpenToken(StartBraceInfo.Token);
    int startLine = StartLine;
    // 1. Для первой строки находим токен и начиная от него сканируем вперед или назад.
    List<ScanTokenInfo> tokInfs = new List<ScanTokenInfo>(
      GetLineTokens(startLine, isScanForward));

    int tokIndex = FindIndex(tokInfs, delegate(ScanTokenInfo ti)
      { return ti.Token.Location == StartBraceInfo.Token.Location; });
    if (tokIndex < 0)
      throw new Exception("tokInfs.IndexOf(startBraceInfo)");

    // Scan first line from "start bracket" index.
    for (int i = tokIndex + 1; i < tokInfs.Count; i++)
      yield return tokInfs[i];

    if (isScanForward) // Scan next lines.
      for (int i = startLine + 1, count = Source.GetLineCount(); i <= count; i++)
        foreach (ScanTokenInfo tokInf in GetLineTokens(i, true))
          yield return tokInf;
    else // Scan previous lines.
      for (int i = startLine - 1; i >= 0; i--)
        foreach (ScanTokenInfo tokInf in GetLineTokens(i, false))
          yield return tokInf;
  }

  #endregion

  #region GetLineTokens()

  /// <summary>
  /// Get tokens of specified line.
  /// </summary>
  /// <param name="line">Line number which tokens it is necessary retrieve</param>
  /// <param name="isForward">Direction of iteration (true is forward)</param>
  /// <returns>Token list</returns>
  List<ScanTokenInfo> GetLineTokens(int line, bool isForward)
  {
    ScanState scanState = GetLineState(line);
    string lineText = Source.GetLine(line - 1);
    Scanner.SetSource(lineText, 0);
    Lex.SetLine(line, lineText, 0, null, null);
    List<ScanTokenInfo> lst = new List<ScanTokenInfo>();

    foreach (ScanTokenInfo var in GetLineTokens(Lex, scanState))
      lst.Add(var.Clone());

    if (!isForward)
      lst.Reverse();

    return lst;
  }

  /// <summary>Return colorer lexer state for specified line.</summary>
  private ScanState GetLineState(int line)
  {
    int state;
    ErrorHandler.ThrowOnFailure(
      ColorState.GetColorStateAtStartOfLine(line, out state));
    return (ScanState)state;
  }

  /// <summary>
  /// Imlementation of GetLineTokens(). Don't use this method directly!
  /// </summary>
  IEnumerable<ScanTokenInfo> GetLineTokens(ScanLexer lex, ScanState scanState)
  {
    ScanTokenInfo info = lex.GetToken(scanState);
    scanState = info.State;
    while (!info.IsEndOfLine)
    {
      yield return info;
      info = lex.GetToken(scanState);
      scanState = info.State;
    }
  }

  #endregion

  #region Paired token identification functions

  public static bool IsOpenToken(Token token)
  {
    if (token is Token.BeginBrace) return true;
    if (token is Token.BeginQuote) return true;
    if (token is Token.BeginRound) return true;
    if (token is Token.BeginSquare) return true;

    if (token is Token.EndBrace) return false;
    if (token is Token.EndQuote) return false;
    if (token is Token.EndRound) return false;
    if (token is Token.EndSquare) return false;

    Token.Keyword kwd = token as Token.Keyword;

    if (kwd != null)
    {
      if (kwd.name == "if") return true;
      if (kwd.name == "else") return false;
    }

    throw new Exception("The token '" + token + "' not a brace!");
  }

  private static bool IsPairedToken(Token token)
  {
    if (token is Token.BeginBrace || token is Token.BeginQuote
      || token is Token.BeginRound || token is Token.BeginSquare
      || token is Token.EndBrace || token is Token.EndQuote
      || token is Token.EndRound || token is Token.EndSquare
    )
      return true;

    Token.Keyword kwd = token as Token.Keyword;

    if (kwd != null && (kwd.name == "if" || kwd.name == "else"))
      return true;

    return false;
  }

  /// <summary>
  /// Return predicate function which check it argument is same token .
  /// </summary>
  private static Predicate<Token> GetMatchBracePredicate(Token token)
  {
    if (token is Token.BeginBrace)
      return delegate(Token t) { return t is Token.BeginBrace; };
    if (token is Token.BeginQuote)
      return delegate(Token t) { return t is Token.BeginQuote; };
    if (token is Token.BeginRound)
      return delegate(Token t) { return t is Token.BeginRound; };
    if (token is Token.BeginSquare)
      return delegate(Token t) { return t is Token.BeginSquare; };

    if (token is Token.EndBrace)
      return delegate(Token t) { return t is Token.EndBrace; };
    if (token is Token.EndQuote)
      return delegate(Token t) { return t is Token.EndQuote; };
    if (token is Token.EndRound)
      return delegate(Token t) { return t is Token.EndRound; };
    if (token is Token.EndSquare)
      return delegate(Token t) { return t is Token.EndSquare; };

    Token.Keyword kwd = token as Token.Keyword;

    if (kwd != null)
    {
      if (kwd.name == "if")
        return delegate(Token t)
        {
          Token.Keyword kwd1 = t as Token.Keyword;

          if (kwd1 != null)
            return kwd1.name == "if";

          return false;
        };
      if (kwd.name == "else")
        return delegate(Token t)
        {
          Token.Keyword kwd1 = t as Token.Keyword;

          if (kwd1 != null)
            return kwd1.name == "else";

          return false;
        };
    }

    return null;
  }

  private static Token GetPairedToken(Token token)
  {
    if (token is Token.BeginBrace) return new Token.EndBrace(true);
    if (token is Token.BeginQuote) return new Token.EndQuote();
    if (token is Token.BeginRound) return new Token.EndRound();
    if (token is Token.BeginSquare) return new Token.EndSquare();

    if (token is Token.EndBrace) return new Token.BeginBrace(true);
    if (token is Token.EndQuote) return new Token.BeginQuote();
    if (token is Token.EndRound) return new Token.BeginRound();
    if (token is Token.EndSquare) return new Token.BeginSquare();

    Token.Keyword kwd = token as Token.Keyword;

    if (kwd != null)
    {
      if (kwd.name == "if")   return new Token.Keyword("else");
      if (kwd.name == "else") return new Token.Keyword("if");
    }

    return null;
  }

  #endregion

  #region Utils

  static T RightHand<T>(IList<T> lst, int index) where T : new()
  {
    int nextIndex = index + 1;
    if (nextIndex >= lst.Count)
      return new T();

    return lst[nextIndex];
  }

  private static List<T> Reverce<T>(IEnumerable<T> seq)
  {
    List<T> lst = new List<T>(seq);
    lst.Reverse();
    return lst;
  }

  static T Find<T>(IEnumerable<T> seq, Predicate<T> predicate) where T : class
  {
    foreach (T item in seq)
      if (predicate(item))
        return item;

    return null;
  }

  static int FindIndex<T>(IList<T> lst, Predicate<T> predicate) where T : class
  {
    for (int i = 0; i < lst.Count; i++)
      if (predicate(lst[i]))
        return i;

    return -1;
  }

  #endregion
}

#endregion
... << RSDN@Home 1.2.0 alpha rev. 637>>
FDSC
FDSC
30.03.2007 12:11
Здравствуйте, IT, Вы писали:

FDS>>Формой с методами обработки информации.

IT>Т.е. написал для отладки отдельный гуй?

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


IT>Всё же я не пойму с чем ты споришь. Если тебя начинает что-то напрягать, то бери и выноси в отдельные методы и классы. Но делать это только ради того, чтобы вынести каждые две строчки в отдельный метод не имеет никакого смысла.

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

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



IT>Не факт, что ты получил незаметную ошибку именно по-этому.


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

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


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



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

Если хотя бы писать сначало всё на псевдокоде — то, убейте меня лучше сразу — не легче ли написать вместо псевдокода сразу названия методов? По крайней мере тогда большинство методов будут списаны с бумажки и мелкой ошибке будет закрасться труднее.
IT
IT
30.03.2007 02:10
Здравствуйте, FDSC, Вы писали:

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


FDS>

FDS>А вы всегда выписываете алгоритм действий на бумажке? Или хотя бы на псевдокоде? И всегда после этого проверяете соответствие кода тому, что написано на бумажке?

На бумажке уже даже не помню когда писал. Писать начинаю в коде сразу, даже ещё не понимая что получится в результате. Единственное, что я стараюсь делать с самого начала — это писать так, чтобы код работал сразу. Т.е. пусть он ещё делает не всё, но уже делает то, что написано, чтобы это можно было запустить и посмотреть. Дальше по шагам начинаем достраивать алгоритм и с каждым таким кирпичиком проверяем результат. При таком подходе опысываемые тобой ошибки исключены.
... << RSDN@Home 1.2.0 alpha rev. 0>>
FDSC
FDSC
30.03.2007 09:10
Здравствуйте, IT, Вы писали:

IT>Здравствуйте, FDSC, Вы писали:


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


FDS>>

FDS>>А вы всегда выписываете алгоритм действий на бумажке? Или хотя бы на псевдокоде? И всегда после этого проверяете соответствие кода тому, что написано на бумажке?

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


Вот до этого места я читал с жутким одобрением и думал, что делаю точно так же

IT> При таком подходе опысываемые тобой ошибки исключены.


А вот этого я не понял

Mirrorer
Mirrorer
02.04.2007 06:03
Здравствуйте, IT, Вы писали:

IT>На бумажке уже даже не помню когда писал. Писать начинаю в коде сразу, даже ещё не понимая что получится в результате. [cut] При таком подходе опысываемые тобой ошибки исключены.


А как можно при таком подходе оценить время необходимое на реализацию ?
"Если Вы отличаетесь от меня, то это ничуть мне не вредит — Вы делаете меня богаче". Экзюпери
VladD2
VladD2
04.04.2007 01:23
Здравствуйте, IT, Вы писали:

FDS>>Формой с методами обработки информации.


IT>Т.е. написал для отладки отдельный гуй?


А почему нет если задача требует? Вот представь, ты должен отдать список иконок другому приложнию (студии, например). Самый прсотой способ убедиться, что с картинками все ОК — склепать примитивную форму с ЛистВью в который выведены иконки.
... << RSDN@Home 1.2.0 alpha rev. 637>>
konsoletyper
konsoletyper
29.03.2007 06:30
Здравствуйте, FDSC, Вы писали:


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


FDS>Вот с этого начнём, точнее закончим. Не согласен


FDS>Где прятаться багам, если методы до отупения просты? Представьте себе, у вас все методы из 3-5 строк. Ну 10 максимум, обычно.

FDS>При этом есть одно условие: название вызываемого метода говорит о том, что этот метод делает, а этот метод делает только то, что сказано в названии (это, кстати, рекомендация Макконнелла). Тогда получаем кучу вот этих самых методов, которые просто и в которых не где прятаться багам, так как, фактически, такой код представляет собой обычный псевдокод.

Всё это замечательно, только на практике когда таких методов несколько десятков, то уже начинаешь банально во всём этом сокровище путаться. Ведь каждый метод может иметь побочные эффекты. И часто бывает думаешь, вот здась кокой из методов: A() ил B() и C() последовательно, — вызывать. С одной стороны, A() имеет больший уровень абстракции A(), чем B() и C(), но с другой стороны, не факт, что у A() нет побочных эффектов, которые приведут к чему-то нехорошему в данном месте. Тогда мы идём в A() и смотрим, что там имеется, и охреневаем. Потому что A() кроме вызова B() и C() содержит так же вызов D() и правит поле q. И понеслись скачки по графу программы... И это всего лишь для написания одного метода!

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

ЛИчно мне такая вот нехорошесть встретилась при написании грида. Конечно, кое-в-чём виноваты MS (руки им за такой кривой бандинг поотрывать мало!), но основные трудности были именно из-за специфики самого контрола. Ну не выражается красиво его логика в терминах ООП! Вообще, написание сложных контролов натолкнуло меня на мысль, что язык вроде C# не соджержит сам по себе хороших абстракций для таких вещей. Что же предложить в замен я не знаю.
... << RSDN@Home 1.2.0 alpha rev. 672>>
FDSC
FDSC
29.03.2007 07:03
Здравствуйте, konsoletyper, Вы писали:

K>Всё это замечательно, только на практике когда таких методов несколько десятков, то уже начинаешь банально во всём этом сокровище путаться. Ведь каждый метод может иметь побочные эффекты. И часто бывает думаешь, вот здась кокой из методов: A() ил B() и C() последовательно, — вызывать. С одной стороны, A() имеет больший уровень абстракции A(), чем B() и C(), но с другой стороны, не факт, что у A() нет побочных эффектов, которые приведут к чему-то нехорошему в данном месте. Тогда мы идём в A() и смотрим, что там имеется, и охреневаем. Потому что A() кроме вызова B() и C() содержит так же вызов D() и правит поле q. И понеслись скачки по графу программы... И это всего лишь для написания одного метода!


Замечательно. Я так НИКОГДА не пишу. В этом весь и смысл. Что у A нет побочных эффектов, у неё есть только тот эффект, который стоит в названии метода, он может оказаться побочным в смысле функционального подхода, но не в смысле логики метода. Иногда бывает, что всё время они лезут (побочные эффекты), но если подумать, лично пока мне всегда удавалось избавиться от них. Иногда это требует написания дополнительно класса.

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

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


Самое интересное, что именно это у меня получается на C#. Причём только на C#.

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

K>ЛИчно мне такая вот нехорошесть встретилась при написании грида. Конечно, кое-в-чём виноваты MS (руки им за такой кривой бандинг поотрывать мало!), но основные трудности были именно из-за специфики самого контрола. Ну не выражается красиво его логика в терминах ООП! Вообще, написание сложных контролов натолкнуло меня на мысль, что язык вроде C# не соджержит сам по себе хороших абстракций для таких вещей. Что же предложить в замен я не знаю.


Можно подробнее. Я не очень понимаю, в чём там дело. А уж по C# я тут выше уже вопил (мне даже смайликов наставили), что не получается на нём нормально программировать
konsoletyper
konsoletyper
30.03.2007 04:49
Здравствуйте, FDSC, Вы писали:

K>>Всё это замечательно, только на практике когда таких методов несколько десятков, то уже начинаешь банально во всём этом сокровище путаться. Ведь каждый метод может иметь побочные эффекты. И часто бывает думаешь, вот здась кокой из методов: A() ил B() и C() последовательно, — вызывать. С одной стороны, A() имеет больший уровень абстракции A(), чем B() и C(), но с другой стороны, не факт, что у A() нет побочных эффектов, которые приведут к чему-то нехорошему в данном месте. Тогда мы идём в A() и смотрим, что там имеется, и охреневаем. Потому что A() кроме вызова B() и C() содержит так же вызов D() и правит поле q. И понеслись скачки по графу программы... И это всего лишь для написания одного метода!


FDS> Замечательно. Я так НИКОГДА не пишу. В этом весь и смысл. Что у A нет побочных эффектов, у неё есть только тот эффект, который стоит в названии метода, он может оказаться побочным в смысле функционального подхода, но не в смысле логики метода. Иногда бывает, что всё время они лезут (побочные эффекты), но если подумать, лично пока мне всегда удавалось избавиться от них. Иногда это требует написания дополнительно класса.


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


В том то всё и дело, что когда у тебя много методов, у тебя много уровней абстракции. A() имеет более высокий уровень, чем B() и C(), потому ты начинаешь путаться при написании нового метода, из каких именно абстракций исходит логика его работы. Это всё при том, что нельзя всё совершенно чётко поделить на уровнии, где-то они могут частично перекрываться.

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

FDS>Мне хотелось бы услышать пример того, какие вещи являются жутко связанными. Честно говоря я себе это очень слабо представляю. Скорее всего это связано с уже "неправильно" разработанными сторонними библиотеками. Я не верю, что если программист может составить алгоритм работы программы, то задача уменьшения связности методов/классов не может быть разрешена. Иначе бы он просто не мог держать в голове весь этот алгоритм.


Ну, программирование редко сводится к осознанию алгоритмов. Уже тем более, проектирвание мало связанно с алгоритмизацией (если это только не специфическая алгоритмически нетривиальная задача). И в голове мало кто что держит. Ведь у человека временная память расчитана в среднем на 7 объектов.

K>>ЛИчно мне такая вот нехорошесть встретилась при написании грида. Конечно, кое-в-чём виноваты MS (руки им за такой кривой бандинг поотрывать мало!), но основные трудности были именно из-за специфики самого контрола. Ну не выражается красиво его логика в терминах ООП! Вообще, написание сложных контролов натолкнуло меня на мысль, что язык вроде C# не соджержит сам по себе хороших абстракций для таких вещей. Что же предложить в замен я не знаю.


FDS>Можно подробнее. Я не очень понимаю, в чём там дело. А уж по C# я тут выше уже вопил (мне даже смайликов наставили), что не получается на нём нормально программировать


Я тоже не совсем понимаю, в чём дело. Когда приступил к проектированию, всё было вроде бы хорошо. Но вот когда написал реализацию, получил запутанный код. Это при том, что и до и после грида я писал разные вещи, и никогда не приходил к такой же путаннице. Постараюсь на выходных провести исследование и ответить на этот вопрос.
... << RSDN@Home 1.2.0 alpha rev. 672>>
FDSC
FDSC
30.03.2007 09:42
Здравствуйте, konsoletyper, Вы писали:

K>В том то всё и дело, что когда у тебя много методов, у тебя много уровней абстракции. A() имеет более высокий уровень, чем B() и C(), потому ты начинаешь путаться при написании нового метода, из каких именно абстракций исходит логика его работы. Это всё при том, что нельзя всё совершенно чётко поделить на уровнии, где-то они могут частично перекрываться.

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

Я вас не понимаю. Видимо, у нас слишком разные области применения программирования.

K>Ну, программирование редко сводится к осознанию алгоритмов. Уже тем более, проектирвание мало связанно с алгоритмизацией (если это только не специфическая алгоритмически нетривиальная задача). И в голове мало кто что держит. Ведь у человека временная память расчитана в среднем на 7 объектов.


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

FDS>>Можно подробнее. Я не очень понимаю, в чём там дело. А уж по C# я тут выше уже вопил (мне даже смайликов наставили), что не получается на нём нормально программировать


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


Будет интересно увидеть ответ
Sinclair
Sinclair
30.03.2007 05:18
Здравствуйте, FDSC, Вы писали:

FDS>Здравствуйте, IT, Вы писали:


IT>>Несколько практических советов от настоящих индейцев.


FDS>


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


FDS>Ммм. А если их сложно напечатать, что делать?

FDS>Например, 4-х мерная матрица 100х100х100х100 (помню, я так и не написал ту программу из-за того, что не смог нормально проанализировать её содержание)
FDS>Или, скажем, граф с представлением в виде списков следования? Это ж замучиться распечатывать! (я форму специальную делал)

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


FDS>Хм. А вот что делать, если мне нужно реализовать процедуру умножения транспонированных матриц (очень просто, неправда ли?).


FDS>Какой из этих вариантов предпочтительней?

Правильный вариант: сделать простой транспонирующий wrapper. Примерно так:
public interface IMatrix<T>
{
  T this[x, y] { get; set;}
    int Width { get; }
    int Height { get; }
}

public class TransposedMatrix<T>: IMatrix<T>
{
  private IMatrix<T> _source;
    protected IMatrix<T> Source { get { return _source; }} 
  private TransposedMatrix(IMatrix<T> source)
    {
      _source = source; 
    }
    public IMatrix<T> Transpose(IMatrix<T> source)
    {
      // avoid double transposing: 
      TransposedMatrix<T> tr = source as TransposedMatrix<T>;
        if (tr!=null)
          return tr.Source;
        return new TransposedMatrix<T>(source);
    }
    
    #region IMatrix<T> implementation
  public T this[x, y] 
    { 
        get { return _source[y, x]; }
        set { _source[y, x] = value; }
    }
        
    int Width { get { return _source.Height; } }
    int Height { get { return _source.Width; } }
    #endregion
}

Предполагаю, что реализация обычной матрицы тривиальна. Теперь, всякий раз когда тебе надо умножить A на Bt, ты так и пишешь A * TransposedMatrix.Transpose(B).
Производительность, скорее всего, будет не слишком хорошей (на текущей версии CLR). Поэтому подобные фрагменты имеет смысл оформить на С++; его компилятор выполнит агрессивный инлайнинг и вся абстрактность уйдет — результирующий код будет не медленнее, чем то, что ты напишешь каждый раз руками.
http://rsdn.org/File/5743/rsdn@home2.gif 1.2.0 alpha rev. 655
MatFiz
MatFiz
31.03.2007 07:09
Здравствуйте, FDSC, Вы писали:

FDS>Хм. А вот что делать, если мне нужно реализовать процедуру умножения транспонированных матриц (очень просто, неправда ли?).

FDS>Варианты реализации:

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

FDS>Минус: понижение производительности. Ведь я мог бы перемножить матрицы без физического транспонирования

Матрицу физически транспонировать и не надо.
Сделай у матрицы флаг транспонированности, а внутри свойства доступа к элементам матрицы меняй индексы местами, если флаг транспонированности установлен.
Или, если боишься if-а при доступе к элементам матрицы, сделай класс "транспонированная матрица", унаследованный от матрицы, имеющий внутри ссылку на исходную матрицу и оверрайдни свойство доступа к элементам.
_Mihail
_Mihail
29.03.2007 06:45
Здравствуйте, IT, Вы писали:

IT>1. Настоящий индеец прежде всего заходит в меню Debug и в диалоге Exceptions включает галку Thrown на CLR Exceptions для managed языков. Это позволяет сэкономить не просто хучу, а туеву хучу времени при поиске ошибок. Отсюда следствие — настоящие индейцы не используют логику на исключениях, иначе весь кайф пропадает.


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


IT>4. Разбиение таких задач на несколько методов, которые используются только один раз не имеет никакого смысла


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


IT>6. Автоматическое тестирование. Настоящие индейцы обязательно пишут тесты для тестирования библиотечного кода, который используется другими частями приложения. Прикладной код тоже иногда может автоматически тестироваться, но это может оказаться банально дорого, т.к. тестирование библиотечного кода и например UI — это принципиально разные вещи. Юнит тест для библиотеки не стоит практически ничего, подготовка и периодическое изменение теста для UI может занять больше времени, чем работа над самим UI.


Золотые слова! Только что-то сомневаюсь, что покрытие библиотечного кода юнит тестами будет отлавливать много багов. У меня лично в таком коде очень редко ошибки появляются, а те что появляются — и без ЮТ о себе заявляют очень быстро, т.к. общие методы используются часто. Но, конечно, и создавать тесты для такого кода очень просто.
IT
IT
29.03.2007 07:09
Здравствуйте, _Mihail, Вы писали:

IT>>1. Настоящий индеец прежде всего заходит в меню Debug и в диалоге Exceptions включает галку Thrown на CLR Exceptions для managed языков. Это позволяет сэкономить не просто хучу, а туеву хучу времени при поиске ошибок. Отсюда следствие — настоящие индейцы не используют логику на исключениях, иначе весь кайф пропадает.


_M>Не знаю что там за ошибки такие ловятся,


Это самый быстрый способ локализовать проблему. Быстрее просто не бывает.

_M>но по крайней мере зверя ContextSwitchDeadLock я буду включать только через свой труп — он мне столько нервов попортил...


Не включай. Я лично вообще такого исключения ни разу в жизни не видел.

IT>>4. Разбиение таких задач на несколько методов, которые используются только один раз не имеет никакого смысла


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


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

_M>Золотые слова! Только что-то сомневаюсь, что покрытие библиотечного кода юнит тестами будет отлавливать много багов. У меня лично в таком коде очень редко ошибки появляются, а те что появляются — и без ЮТ о себе заявляют очень быстро, т.к. общие методы используются часто. Но, конечно, и создавать тесты для такого кода очень просто.


Редкость и быстрота отлова — это плохие отговорки. Достаточно однажды, например, написать новый функционал, который слегка где-нибудь сломает старый, закомитить результат и уехать в отпуск на месяц. Если такой баг заблокирует работу нескольких человек, то либо они достанут тебя с берега лазурного моря и испортят весь кайф, либо порвут тебе жопу на немецкий крест когда ты вернёшься. Другими словами, дело не в простоте отлова и редкости. Дело в уровне ответственности. Чем чаще используется один и тот же код, тем больше должно быть контроля, что его изменение не приведёт к фатальным или блокирующим последствиям.
... << RSDN@Home 1.2.0 alpha rev. 0>>
_Mihail
_Mihail
29.03.2007 07:34
Здравствуйте, IT, Вы писали:

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


Тоже довод
VladD2
VladD2
04.04.2007 01:37
Здравствуйте, IT, Вы писали:

_M>>но по крайней мере зверя ContextSwitchDeadLock я буду включать только через свой труп — он мне столько нервов попортил...


IT>Не включай. Я лично вообще такого исключения ни разу в жизни не видел.


Это похоже то из-за чего у нас отладка под студией навертывается .
... << RSDN@Home 1.2.0 alpha rev. 637>>
Ракопаукодав
Ракопаукодав
30.03.2007 09:44
Здравствуйте, IT, Вы писали:
<skipped>

Это советы для кодирования, а спрашивали рекомендации по проектированию
Andrei N.Sobchuck
Andrei N.Sobchuck
30.03.2007 09:49
Здравствуйте, Ракопаукодав, Вы писали:

Р>Это советы для кодирования, а спрашивали рекомендации по проектированию


1. Семь раз отмерь — один отрежь.
FDSC
FDSC
30.03.2007 10:31
Здравствуйте, Andrei N.Sobchuck, Вы писали:

ANS>Здравствуйте, Ракопаукодав, Вы писали:


Р>>Это советы для кодирования, а спрашивали рекомендации по проектированию


ANS>1. Семь раз отмерь — один отрежь.


А чем мерять? В смысле, какие средства при проектировании использовать?

P.S. Опять же, если в фирме начальство строго относится к пиратскому ПО, то просто так не попроектируешь...
aka50
aka50
30.03.2007 10:44
Здравствуйте, FDSC, Вы писали:

FDS>А чем мерять? В смысле, какие средства при проектировании использовать?

FDS>P.S. Опять же, если в фирме начальство строго относится к пиратскому ПО, то просто так не попроектируешь...

Ну почему-же... полно бесплатных или достаточно недорогих продуктов... вопрос только насколько навороченная среда нужна . Например можно что-то поднобное http://argouml.tigris.org/features.html найти...
prVovik
prVovik
30.03.2007 11:47
Здравствуйте, FDSC, Вы писали:

FDS>А чем мерять? В смысле, какие средства при проектировании использовать?


FDS>P.S. Опять же, если в фирме начальство строго относится к пиратскому ПО, то просто так не попроектируешь...


Проектировать — это не значит рисовать что-нибудь в какой-нибудь навороченной рисовалке схем. Можно сказать, что спроектировать — это написть ту же самую программу, но с очень низкой степенью детализации. А уж "программировать" с низкой степенью детализации можно разными способами (ИМХО — в порядке убывания полезности):
1) Используя привычный язык программирования для описания основных интерфейсов, классов, и, возможно, программного кода, который в очень крупном масштабе записывает самые важные и крупные действия, которые должны происходить в программе.
2) Написать программу в крупном масштабе можно на бумажке карандашом, либо в специализированной рисовалке произвользых схем, используя при этом какие угодно картинки.
3) UML в специализированном редакторе (RR, Together, и т.д.)
FDSC
FDSC
30.03.2007 12:17
Здравствуйте, prVovik, Вы писали:

V>Здравствуйте, FDSC, Вы писали:


FDS>>А чем мерять? В смысле, какие средства при проектировании использовать?


FDS>>P.S. Опять же, если в фирме начальство строго относится к пиратскому ПО, то просто так не попроектируешь...


V>Проектировать — это не значит рисовать что-нибудь в какой-нибудь навороченной рисовалке схем. Можно сказать, что спроектировать — это написть ту же самую программу, но с очень низкой степенью детализации. А уж "программировать" с низкой степенью детализации можно разными способами (ИМХО — в порядке убывания полезности):

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

Угу, только вот почему-то потом эти действия всегда оказываются не такими Да и если записывать такие действия в коде, не видно, кто к кому обращается, очень велик риск потерять информационные связи.
Andrei N.Sobchuck
Andrei N.Sobchuck
30.03.2007 12:15
Здравствуйте, FDSC, Вы писали:


FDS>А чем мерять? В смысле, какие средства при проектировании использовать?


Вот г-н Евгений бумагу, карандаш и ластик пользует. Меряет, наверное, линейкой

А смысл "семь раз отмерь — один отрежь" — перед тем как писать, нужно подумать. Я говорю не об конструировании иерархии (ибо сам не умею зараннее классы продумывать), а о понимании самой задачи. Непонимание задачи делает бессмысленым любые архитектурные изыски. Если есть сомнения, то сделай прототип(ы). Решение обычно оформляется, когда ты наберёшь некоторую критическую массу знаний, как по самой задаче, так и по технологиям, которые можно применить.

2. Стремись к красивым решениям. Когда говорят, если бы было время я бы сделал красиво, а так как его не было, сделал уродливо — не верь. Красивое решение делается за минимальное время и минимальным количеством усилий. Именно по этому оно и красиво. На микроуровне можно пользоваться формальными правилами типа LSP или закона Деметра (есть, кстати, подобные правила для ФЯ?), но макроуровне тебе поможет только твоё чувство прекрасного.

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

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

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

Получается, что все, что нужно это твоя голова. Если её нет, то никакая тулза не поможет Для усовершенствования работы головы есть разные методики, типа ТРИЗа. Другое дело, я не знаю, насколько они помогают Ну, и практика-обратная связь, практика-обратная связь, практика-обратная связь.

Я еще пользуюсь универсальными философскими правилами:

1. Разделяй и властвуй. Следствие из особенности человеческого мозга "7+-2". Поскольку всю архитект`уру в целом тяжело удержать в голове, то задачу бьют на части. Тут нужно читать о функциональной/объектной декомпозиции. Кстати, вывод: чем меньше ответсвенности на классе (или другой единице декомпозиции), и с чем меньшим числом других классов идёт взаимодействие, тем его проще и придумать и реализовать.

2. Стой на плечах у гигантов. Применяй, то что придумали до тебя другие. Есть там правила, типа "Первое правило создания распределённых приложений — не распределяй". Ну, и прочая мудрость, которая поможет тебе не ходить по всеизвестным граблям. Мне, например, нравится ОО: тотальный messaging, рекурсивный дизайн. Постепенно всё идёт, как минимум к тотальному messaging, кроме того есть правила, которые удовлетворяют моему чувству прекрасного. Так что я вижу смысл пользоваться этим прямо сейчас, но я не навязываю
FDSC
FDSC
30.03.2007 12:36
Здравствуйте, Andrei N.Sobchuck, Вы писали:

ANS>А смысл "семь раз отмерь — один отрежь" — перед тем как писать, нужно подумать. Я говорю не об конструировании иерархии (ибо сам не умею зараннее классы продумывать), а о понимании самой задачи. Непонимание задачи делает бессмысленым любые архитектурные изыски. Если есть сомнения, то сделай прототип(ы). Решение обычно оформляется, когда ты наберёшь некоторую критическую массу знаний, как по самой задаче, так и по технологиям, которые можно применить.


Не поверишь. Буквально в конце августа я писал одну прогу на C#, смысл которой был в предоставлении интерфейса подключаемым библиотекам и управлении данными этих библиотек (т.е. сохранение доп. информации этих библиотек в том же файле, что и осн. информация).
Ну вроде всё описал. На бумаге (*.txt) столько исписал всего, что даже смотреть страшно. Всё равно, когда я начал кодировать загрузку межбиблиотечных данных (самое последнее, что надо было сделать), оказалось, что я: 1. Не сохранил нужную информацию о обработчике информации 2. Вообще вся необходимая информация находится в другом модуле, является приватной и ещё не загружена 3. При десериализации порядок вызова методов обработчиков информации неправильный и приводит к ошибкам. И вот тут мне было очень плохо
А ведь расписал всё так, казалось, что неожиданностей быть не должно. А вот нет, упустил.

ANS>3. Лучшее враг хорошего. Если тебе кажется, что ты придумал ерунду, но незнаешь как сделать лучше, не бойся воплощать решение в жизнь, так как кривое решение лучше чем никакого. Со временем у тебя появятся нужные знания (если ты к ним стремишся, естественно), и ты недопустишь подобных просчетов.


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

ANS>Получается, что все, что нужно это твоя голова. Если её нет, то никакая тулза не поможет Для усовершенствования работы головы есть разные методики, типа ТРИЗа. Другое дело, я не знаю, насколько они помогают Ну, и практика-обратная связь, практика-обратная связь, практика-обратная связь.


Голова с тулзой лучше головы без тулзы

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


К сожалению, всё это мало помогает. Вопрос ведь не в том, что бы знать эти правила, а в том, чтобы получалось их соблюдать при проектировании. Потому что страешься их соблюдать, а потом оказывается, что не соблюл
Andrei N.Sobchuck
Andrei N.Sobchuck
30.03.2007 01:03
Здравствуйте, FDSC, Вы писали:

FDS> 1. Не сохранил нужную информацию о обработчике информации


К архитектуре это не имеет отношения.

FDS> 2. Вообще вся необходимая информация находится в другом модуле, является приватной и ещё не загружена


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


FDS>3. При десериализации порядок вызова методов обработчиков информации неправильный и приводит к ошибкам. И вот тут мне было очень плохо


Пиши тесты

FDS> К сожалению, всё это мало помогает. Вопрос ведь не в том, что бы знать эти правила, а в том, чтобы получалось их соблюдать при проектировании. Потому что страешься их соблюдать, а потом оказывается, что не соблюл


Работай над собой

Кстати, есть методики позволяющие "менять" себя. Типа всяких "ауто-", НЛП. Так вот, у них у всех есть одна общая черта, перед тем как работать над собой нужно обдумать, что ты хочеш получить (обычно записать на бумаге на бумаге), поработать над собой, записать что получилось. Новый цикл. Только это не проблемы архитектуры.
FDSC
FDSC
30.03.2007 02:00
Здравствуйте, Andrei N.Sobchuck, Вы писали:

ANS>Здравствуйте, FDSC, Вы писали:


FDS>> 1. Не сохранил нужную информацию о обработчике информации


ANS>К архитектуре это не имеет отношения.


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

FDS>> 2. Вообще вся необходимая информация находится в другом модуле, является приватной и ещё не загружена


ANS>Вот-вот. Этого ты не знал. А мог бы написать тест, который выводил список доступной тебе информации в модуле. Но, опять же, какое влияние на архитектуру.


Какой ещё тест? Я сам проектировал этот модуль, но когда проектировал и писал ещё не знал, что информация этого модуля мне понадобится. Хотя специально смотрел, какая информация понадобится, про эту забыл.

FDS>>3. При десериализации порядок вызова методов обработчиков информации неправильный и приводит к ошибкам. И вот тут мне было очень плохо


ANS>Пиши тесты


Опять же, что же это за тест, который тестирует архитектуру? Я просто взял и так спроектировал программу, что порядок загрузки информации был неправильный. Причём этот порядок определялся именно архитектурой, а не порядком вызова некоторых функций в методе. Для его исправления пришлось довольно много всего писать.
bkat
bkat
30.03.2007 12:15
Здравствуйте, FDSC, Вы писали:

FDS>P.S. Опять же, если в фирме начальство строго относится к пиратскому ПО, то просто так не попроектируешь...


Что нормально проектировать можно только в тулах за многие килобаксы?
Нормально проектировать можно и на листочке бумаги.
Если нету светлых мыслей, то никакой тул не поможет.

Уже сколько раз наблюдал, как народ свято верил,
что с появлением крутого тула (вполне хорошего, кстати)
все проблемы со спецификациями, дизайном и пр... уйдут автоматически.
А вот не уходят...
Фигня, поданная на вход самому замечательному тулу, не перестает быть фигней...
FDSC
FDSC
30.03.2007 12:39
Здравствуйте, bkat, Вы писали:

B>Что нормально проектировать можно только в тулах за многие килобаксы?

B>Нормально проектировать можно и на листочке бумаги.
B>Если нету светлых мыслей, то никакой тул не поможет.

Может расскажите, как "нормально проектировать"?
bkat
bkat
30.03.2007 12:52
Здравствуйте, FDSC, Вы писали:

FDS>Здравствуйте, bkat, Вы писали:


B>>Что нормально проектировать можно только в тулах за многие килобаксы?

B>>Нормально проектировать можно и на листочке бумаги.
B>>Если нету светлых мыслей, то никакой тул не поможет.

FDS>Может расскажите, как "нормально проектировать"?


Хм...
Думаешь это влезет в 10 строк сообщения на форуме?
Ты точно так же можешь попросить рассказать о том, как писать хорошие программы.

Советы их книжек ты и сам наверняка знаешь...
А все остальное — это опыт, который ты сам должен получить.
sharcUs
sharcUs
30.03.2007 01:01
Здравствуйте, FDSC, Вы писали:

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

1. Показать как можно это сделать.
2. Указать существующие модели и паттерны.
3. Заставить мозг думать.

Уверен, однако, что как бы внимательно вы не изучали подобную литературу, некоторые моменты вы обязательно упустите или не обратите внимания.
Научиться "нормально проектировать" возможно только путем собственных проб и ошибок, чем больше бы будете пробовать, тем опытнее вы будете.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
IT
IT
30.03.2007 02:16
Здравствуйте, Ракопаукодав, Вы писали:

Р>Это советы для кодирования, а спрашивали рекомендации по проектированию


Советую ещё раз внимательно перечитать вопрос.

ЗЫ. Я специально старался опускать вопросы проектирования и архитектуры, т.к. умных общих фраз тут и без меня навыдают. Только, к сожалению, по моим наблюдениям между умением говорить такие банальности и умением их применять лежит огромная пропасть, мостиком через которую как раз и являются практические приёмы и навыки.
... << RSDN@Home 1.2.0 alpha rev. 0>>
Mirrorer
Mirrorer
02.04.2007 06:42
Здравствуйте, IT, Вы писали:

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


В самую точку.
"Если Вы отличаетесь от меня, то это ничуть мне не вредит — Вы делаете меня богаче". Экзюпери
VladD2
VladD2
04.04.2007 01:23
Здравствуйте, IT, Вы писали:

IT>1. Настоящий индеец прежде всего заходит в меню Debug и в диалоге Exceptions включает галку Thrown на CLR Exceptions для managed языков. Это позволяет сэкономить не просто хучу, а туеву хучу времени при поиске ошибок. Отсюда следствие — настоящие индейцы не используют логику на исключениях, иначе весь кайф пропадает.


Жаль мы с тобой все же не всегда настоящие индейцы. Потому как после того как ты в Интеграции сам знаешь чего реализовал комбы со списками типов и методов в редактироуемом файле, то сразу стала проявляться суть настоящего индусяцкого кода лежащего в:
...\VsSDK\2007.02\VisualStudioIntegration\Common\Source\CSharp\LanguageService\CodeWindowManager.cs
// Omitting any of the following operator overloads
// violates FxCop rule: IComparableImplementationsOverrideOperators.
/// <include file='doc\CodeWindowManager.uex' path='docs/doc[@for="DropDownMember.Operator=="]/*' />
public static bool operator ==(DropDownMember m1, DropDownMember m2) {
        return m1.Equals(m2);
}

приводящего к ловине:
System.NullReferenceException occurred
  Message="Object reference not set to an instance of an object."
  Source="Microsoft.VisualStudio.Package.LanguageService"
  StackTrace:
       at Microsoft.VisualStudio.Package.DropDownMember.op_Equality(DropDownMember m1, DropDownMember m2)


Вот так один индус может испортить жизнь двум индейцам.
... << RSDN@Home 1.2.0 alpha rev. 637>>
IT
IT
04.04.2007 01:52
Здравствуйте, VladD2, Вы писали:

VD>Вот так один индус может испортить жизнь двум индейцам.


Это точно. Приходится NullReferenceException отключать.
... << RSDN@Home 1.2.0 alpha rev. 0>>