Должен ли я добавить избыточный код сейчас, если это может понадобиться в будущем?

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

Например, я сейчас работаю над мобильным приложением, которое имеет этот кусок кода:

public static CalendarRow AssignAppointmentToRow(Appointment app, List<CalendarRow> rows)
{
    //1. Is rows equal to null? - This will be the case if this is the first appointment.
    if (rows == null) {
        rows = new List<CalendarRow> ();
    }

    //2. Is rows empty? - This will be the case if this is the first appointment / some other unknown reason.
    if(rows.Count == 0)
    {
        rows.Add (new CalendarRow (0));
        rows [0].Appointments.Add (app);
    }

    //blah...
}

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

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

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

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

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

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

TLDR: Должен ли я заходить так далеко, чтобы добавить избыточный код, чтобы сделать его потенциально более надежным в будущем?

114 голосов | спросил KidCode 27 MarpmSun, 27 Mar 2016 20:51:46 +03002016-03-27T20:51:46+03:0008 2016, 20:51:46

16 ответов


177

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

Вызовите первое условие A и второе условие B.

Вы сначала скажете:

  

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

То есть: A подразумевает B, или в более основных терминах (NOT A) OR B

И затем:

  

Я не могу думать о какой-либо причине, почему первый раздел будет ложным, а второй раздел - значением true, что делает ненужным второй оператор if.

То есть: NOT((NOT A) AND B). Примените закон Деморгана, чтобы получить (NOT B) OR A, который является B, подразумевает A.

Следовательно, если оба ваших утверждения истинны, то A означает, что B и B подразумевают A, что означает, что они должны быть равны.

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

Итак, теперь вопрос: как написать код? Реальный вопрос: какой заявленный договор метода ? Вопрос о том, являются ли условия избыточными, - это красная селедка. Реальный вопрос: «Я разработал разумный контракт, и мой метод четко реализует этот контракт?»

Давайте посмотрим на объявление:

public static CalendarRow AssignAppointmentToRow(
    Appointment app,    
    List<CalendarRow> rows)

Это общедоступно, поэтому оно должно быть надежным для плохих данных от произвольных абонентов.

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

И все же имя метода - это глагол, предполагающий, что он полезен для его побочного эффекта.

Контракт параметра списка:

  • Нулевой список в порядке
  • Список с одним или несколькими элементами в нем ОК
  • Список без элементов в нем неверен и не может быть возможным.

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

Мой совет: начните сначала. У этого API есть коннекторный интерфейс, написанный на нем. (Выражение из старой истории о конфетных машинах в Microsoft, где и цены, и выбор являются двузначными числами, и очень легко ввести «85», что является ценой пункта 75, и вы получаете неправильный элемент. Интересный факт: да, я действительно сделал это по ошибке, когда пытался вытащить резину из торгового автомата в Microsoft!)

Вот как создать разумный контракт:

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

Не принимать изменяемые типы в качестве входов, например списков; если вам нужна последовательность информации, возьмите IEnumerable. Только прочитайте последовательность; не записывайте в собранную коллекцию, если не указано very , что это контракт метода. Принимая IEnumerable, вы отправляете сообщение вызывающему, что вы не собираетесь мутировать их коллекцию.

Никогда не принимать значения null; нулевая последовательность - мерзость. Требовать, чтобы вызывающий передал пустую последовательность, если это имеет смысл, никогда не имеет значения null.

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

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

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

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

  • Получить инструмент для тестирования. Убедитьсяваши тесты охватывают каждую строку кода. Если есть непокрытый код, либо у вас есть недостающий тест, либо у вас есть мертвый код. Мертвый код на удивление опасен, потому что обычно он не предназначен для смерти! Неповторимая ужасная ошибка безопасности «goto fail» Apple в течение пары лет назад сразу приходит на ум.

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

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

ответил Eric Lippert 28 MaramMon, 28 Mar 2016 04:47:44 +03002016-03-28T04:47:44+03:0004 2016, 04:47:44
89

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

Оба оператора if проверяют разные вещи. Оба являются подходящими испытаниями в зависимости от ваших потребностей.

Раздел 1 проверяет и корректирует объект null. Боковое примечание: Создание списка не создает дочерние элементы (например, CalendarRow).

Раздел 2 проверяет и исправляет ошибки пользователя и /или реализации. Просто потому, что у вас есть List<CalendarRow>, не означает, что у вас есть какие-либо элементы в списке. Пользователи и разработчики будут делать то, что вы не можете себе представить, просто потому, что им разрешено это делать, независимо от того, имеет ли это смысл для вас или нет.

ответил Adam Zuckerman 27 MarpmSun, 27 Mar 2016 21:39:23 +03002016-03-27T21:39:23+03:0009 2016, 21:39:23
35

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

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

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

ответил cmaster 27 MarpmSun, 27 Mar 2016 21:04:30 +03002016-03-27T21:04:30+03:0009 2016, 21:04:30
23

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

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

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

Для большего количества мыслей прочитайте это короткое эссе: Не прибивайте свою программу в вертикальном положении


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

// Calculates the nth Fibonacci number
int fibonacci(int n) {
    int a = 0;
    int b = 1;

    for(int i = 0; i < n; i++) {
        int temp = b;
        b = a + b;
        a = temp;
    }

    return b;
}

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

Возможно, возникает соблазн написать такую ​​функцию:

// Calculates the nth Fibonacci number
int fibonacci(int n) {
    int a = 0;
    int b = 1;

    // Make sure the input is nonnegative
    if(n < 0) {
        n = 1; // Replace the negative input with an input that will work
    }

    for(int i = 0; i < n; i++) {
        int temp = b;
        b = a + b;
        a = temp;
    }

    return b;
}

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

Вместо этого лучше написать чек, например:

// Calculates the nth Fibonacci number
int fibonacci(int n) {
    int a = 0;
    int b = 1;

    // Make sure the input is nonnegative
    if(n < 0) {
        throw new ArgumentException("Can't have negative inputs to Fibonacci");
    }

    for(int i = 0; i < n; i++) {
        int temp = b;
        b = a + b;
        a = temp;
    }

    return b;
}

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

ответил Kevin 28 MaramMon, 28 Mar 2016 01:03:46 +03002016-03-28T01:03:46+03:0001 2016, 01:03:46
11

Следует ли добавить избыточный код? Нет.

Но то, что вы описываете, не является избыточным кодом .

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

Лично я очень верю в эту методологию, но, как и все, вы должны быть осторожны. Возьмем, к примеру, код C ++ std::vector::operator[]. Включение на некоторое время реализации режима отладки VS, эта функция не выполняет проверку границ. Если вы запрашиваете элемент, который не существует, результат не определен. Пользователь должен предоставить действительный векторный индекс. Это довольно преднамеренно: вы можете «выбрать» для проверки границ, добавив его на callsite, но если бы выполнялась реализация operator[], то вы не смогли бы «отказаться» от того, , Как функция с низким уровнем, это имеет смысл.

Но если вы писали функцию AddEmployee(string name) для некоторого интерфейса более высокого уровня, я бы полностью ожидал, что эта функция по крайней мере исключит исключение, если вы предоставили пустой name, а также это предварительное условие документируется непосредственно над объявлением функции. Сегодня вы не можете предоставлять неаналитированный пользовательский ввод для этой функции, но таким образом, чтобы сделать его «безопасным», это означает, что любые нарушения предварительных условий, которые появляются в будущем, могут быть легко диагностированы, а не потенциально инициировать цепочку домино труднодоступных, обнаруживать ошибки. Это не избыточность: это усердие.

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

  • живет на языке сверхвысокого уровня (скажем, JavaScript, а не C)
  • находится на границе интерфейса
  • не критичен по производительности
  • напрямую принимает вход пользователя

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

Этот раздел рассматривается далее в Википедии ( https://en.wikipedia.org/wiki/Defensive_programming ).

ответил Lightness Races in Orbit 28 MarpmMon, 28 Mar 2016 19:09:50 +03002016-03-28T19:09:50+03:0007 2016, 19:09:50
9

Здесь важны две из десяти команд программирования:

  • Вы не должны полагать, что вход правильный

  • Вы не должны использовать код для дальнейшего использования

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

Проверка нулевого значения не имеет ничего общего с будущим использованием. Это связано с Законом №1: не предполагайте, что ввод будет правильным. Никогда не предполагайте, что ваша функция получит некоторое подмножество ввода. Функция должна реагировать логически, независимо от того, как фиктивный и перепутал входы.

ответил Tyler Durden 27 MarpmSun, 27 Mar 2016 23:15:58 +03002016-03-27T23:15:58+03:0011 2016, 23:15:58
7

Определение «избыточного кода» и «YAGNI» часто зависит от того, насколько далеко вы смотрите вперед.

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

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

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

ответил Ewan 27 MarpmSun, 27 Mar 2016 21:05:29 +03002016-03-27T21:05:29+03:0009 2016, 21:05:29
6

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

/** ...
*   Precondition: rows is null or nonempty
*/
public static CalendarRow AssignAppointmentToRow(Appointment app, List<CalendarRow> rows)
{
    Assert( rows==null || rows.Count > 0 )
    //1. Is rows equal to null? - This will be the case if this is the first appointment.
    if (rows == null) {
        rows = new List<CalendarRow> ();
        rows.Add (new CalendarRow (0));
        rows [0].Appointments.Add (app);
    }

    //blah...
}

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

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

ответил Theodore Norvell 27 MarpmSun, 27 Mar 2016 22:21:53 +03002016-03-27T22:21:53+03:0010 2016, 22:21:53
5

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

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

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

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

ответил gnasher729 27 MarpmSun, 27 Mar 2016 22:29:01 +03002016-03-27T22:29:01+03:0010 2016, 22:29:01
3

Основной вопрос здесь: «что произойдет, если вы это сделаете /не делаете»?

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

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

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

Все ваши «ненужные» решения должны приниматься с учетом этой предпосылки.

ответил deworde 28 MarpmMon, 28 Mar 2016 12:39:10 +03002016-03-28T12:39:10+03:0012 2016, 12:39:10
2

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

Интерфейсы должны быть простыми, и они должны быть относительно стабильными - особенно в том, что-то вроде общедоступного статического метода public static

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

if (rows == null) throw new ArgumentNullException(nameof(rows));

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

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

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

Основная проблема заключается в том, что любая абстракция, которую вы делаете, будет протекать («Я хотел напечатать arre, а не fore! Stupid spell-checker!»), а код для обработки всех особых случаев и исправлений код, который необходимо сохранить и понять, и код, который вам нужно проверить. Сравните усилия, направленные на то, чтобы удостовериться, что не нулевой список передан с исправлением ошибки, которую у вас есть год спустя на производстве, - это не очень хороший компромисс. В идеальном мире вы хотите, чтобы каждый метод работал исключительно с собственным входом, возвращая результат и не изменяя глобального состояния. Конечно, в реальном мире вы найдете множество случаев, когда это not самое простое и ясное решение (например, при сохранении файла), но я считаю, что сохранение методов «чистым», когда нет причина для их чтения или манипулирования глобальным состоянием делает код намного легче рассуждать. Это также дает вам более естественные точки для разделения ваших методов :)

Это не значит, что всенепредвиденное должно привести к краху вашего приложения, наоборот. Если вы хорошо используете исключения, они, естественно, образуют безопасные точки обработки ошибок, где вы можете восстановить стабильное состояние приложения и позволить пользователю продолжать то, что они делают (в идеале, избегая потери данных для пользователя). В этих точках обработки вы увидите возможности для устранения проблем («Нет номера заказа 2212. Возможно, вы имели в виду 2212b?») Или предоставления пользователю контроля («Ошибка подключения к базе данных. Попробуйте еще раз?»). Даже если такой вариант недоступен, по крайней мере, это даст вам шанс, что ничто не повреждено - я начал оценивать код, который использует using и try ... finally намного больше, чем finally ... try, это дает вам много возможностей поддерживать инварианты даже в исключительных условиях.

Пользователи не должны терять свои данные и работать. Это все равно должно быть сбалансировано с затратами на разработку и т. Д., Но это довольно хорошее общее руководство (если пользователи решают, покупать ли ваше программное обеспечение или нет, внутреннее программное обеспечение обычно не имеет такой роскоши). Даже полное сбой приложения становится намного меньше проблем, если пользователь может просто перезапустить и вернуться к тому, что они делают. Это настоящая надежность - Word сохраняет вашу работу все время без , повреждая ваш документ на диске и предоставляя вам вариант для восстановления этих изменений после перезапуска Word после сбоя. Это лучше, чем отсутствие ошибки в первую очередь? Наверное, нет - хотя не забывайте, что работу, потраченную на ловлю редкой ошибки, можно было бы лучше потратить повсюду. Но это намного лучше, чем альтернативы - например, поврежденный документ на диске, вся работа с момента последнего сохранения потеряна, документ автоматически заменен на изменения перед сбоем, который только что оказался Ctrl + A и Delete.

ответил Luaan 29 MaramTue, 29 Mar 2016 11:36:02 +03002016-03-29T11:36:02+03:0011 2016, 11:36:02
1

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

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

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

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

ответил JeffO 28 MarpmMon, 28 Mar 2016 18:54:47 +03002016-03-28T18:54:47+03:0006 2016, 18:54:47
1

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

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

Ответ - нет. Вы всегда должны делать исключение в случае неожиданного ввода. Рассмотрим это: если некоторые другие части кода нарушают математическое ожидание (т. Е. Контракт) вашего метода , это означает, что есть ошибка . Если есть ошибка, вы хотите узнать ее как можно раньше, чтобы вы могли ее исправить, и исключение поможет вам это сделать.

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

Итак, код должен выглядеть так:

public static CalendarRow AssignAppointmentToRow(Appointment app, List<CalendarRow> rows)
{
    if (rows != null && rows.Count == 0) throw new ArgumentException("Rows is empty."); 

    //1. Is rows equal to null? - This will be the case if this is the first appointment.
    if (rows == null) {
        rows = new List<CalendarRow> ();
        rows.Add (new CalendarRow (0));
        rows [0].Appointments.Add (app);
    }

    //blah...
}

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


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

ответил JacquesB 30 MaramWed, 30 Mar 2016 10:15:48 +03002016-03-30T10:15:48+03:0010 2016, 10:15:48
1
  

Должен ли я добавить избыточный код сейчас, если это может понадобиться в будущем?

Вы не должны добавлять избыточный код в любое время.

Вы не должны добавлять код, который нужен только в будущем.

Вы должны убедиться, что ваш код ведет себя хорошо, что бы ни случилось.

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

Пример псевдокода:

file = File.open(">", "bla")  or raise "Paranoia: cannot open file 'bla'"

file.write("xytz") or raise "Paranoia: disk full?"

file.close()  or raise "Paranoia: huh?!?!?"

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

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

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

ответил AnoE 1 PMpFri, 01 Apr 2016 13:58:00 +030058Friday 2016, 13:58:00
-1

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

Сначала заголовок метода:

public static CalendarRow AssignAppointmentToRow(Appointment app, List<CalendarRow> rows)

Назначить встречу , что строка? Это должно быть ясно сразу из списка параметров. Без каких-либо дополнительных знаний я бы ожидал, что параметры метода выглядят следующим образом: (Appointment app, CalendarRow row).

Далее, «входные проверки»:

//1. Is rows equal to null? - This will be the case if this is the first appointment.
if (rows == null) {
    rows = new List<CalendarRow> ();
}

//2. Is rows empty? - This will be the case if this is the first appointment / some other unknown reason.
if(rows.Count == 0)
{
    rows.Add (new CalendarRow (0));
    rows [0].Appointments.Add (app);
}

Это дерьмо.

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

Но вся концепция назначения встреч где-то странная (если это не часть GUI кода). Кажется, что ваш код содержит (или, по крайней мере, пытается) явную структуру данных, представляющую календарь (то есть List<CalendarRows> <-, который должен быть определен как Calendar где-то, если вы хотите пойти этим путем, тогда вы передадите Calendar calendar в свой метод). Если вы пойдете так, я бы ожидал, что calendar будет заполнен слотами, в которые вы помещаете (назначаете) встречи после этого (например, calendar[month][day] = appointment будет подходящим кодом). Но тогда вы также можете полностью исключить структуру календаря из основной логики и просто иметь List<Appointment>, где объекты Appointment содержат атрибут date. И тогда, если вам нужно отобразить календарь где-нибудь в графическом интерфейсе, вы можете создать эту структуру «явного календаря» непосредственно перед рендерингом.

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

ответил clime 1 AMpFri, 01 Apr 2016 00:35:58 +030035Friday 2016, 00:35:58
-2

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

псевдокод:

let C_now   = cost of implementing the piece of code now
let C_later = ... later
let C_maint = cost of maintaining the piece of code one day
              (note that it can be negative)
let P_need  = probability that you will need the code after N days

if C_now + C_maint * N < P_need*C_later then implement the code else omit it.

Факторы для C_maint:

  • Улучшает ли он код вообще, делая его более самодокументируемым, легче тестировать? Если да, то отрицательный C_maint ожидается
  • Делает ли код более значительным (следовательно, его сложнее читать, дольше компилировать, выполнять тесты и т. д.)?
  • Ожидаются ли какие-либо реорганизации /редизайны? Если да, bump C_maint. Этот случай требует более сложной формулы, с большим количеством переменных, таких как N.

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

ответил Vi0 29 MarpmTue, 29 Mar 2016 17:07:51 +03002016-03-29T17:07:51+03:0005 2016, 17:07:51

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132