Элегантный способ обработки «невозможных» путей кода - PullRequest
3 голосов
/ 30 сентября 2010

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

activeGames = [10, 20, 30]
limit = 4

def getBestActiveGameStat():
    if not activeGames: return None
    return max(activeGames)

def bah():
    if limit == 0: return "Limit is 0"

    if len(activeGames) >= limit:
        somestat = getBestActiveGameStat()
        if somestat is None:
            print "The universe has exploded"
        #etc...

Что будет происходить в линии взрыва вселенной?Если предел равен 0, то функция возвращает.Если len(activeGames) >= limit, то должна быть хотя бы одна активная игра, поэтому getBestActiveGameStat() не может вернуть None.Итак, я должен даже проверить это?

То же самое происходит и с чем-то вроде цикла while, который всегда возвращается в цикл:

def hmph():
    while condition:
        if foo: return "yep"
        doStuffToMakeFooTrue()

    raise SingularityFlippedMyBitsError()

Поскольку я "знаю", это невозможно,что-нибудь еще там?

Ответы [ 2 ]

4 голосов
/ 01 октября 2010

Если len (activeGames)> = limit, тогда должна быть хотя бы одна активная игра, поэтому getBestActiveGameStat () не может вернуть None.Так стоит ли мне это проверять?

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

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

Быстро написанное утверждение assert может выразить ожидаемый инвариант для читателей-людей.А при отладке ошибочное утверждение может быстро определить ошибку.

Саттер и Александреску решают эту проблему в «Стандартах кодирования C ++».Несмотря на название, их аргументы и рекомендации не зависят от языка.

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

Например, если регистр default в операторе switch не может произойти, добавьте регистр с помощью assert (false).

2 голосов
/ 30 сентября 2010

ИМХО, первый пример - это больше вопрос о том, как пользователю представляются катастрофические сбои.В случае, если кто-то делает что-то действительно глупое и не устанавливает activeGames ни на одно, большинство языков генерирует исключение типа NullPointer / InvalidReference.Если у вас есть хорошая система для выявления ошибок такого рода и их элегантной обработки, то я бы сказал, что вы полностью исключите эти охранники.

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

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

withTiming(hmph, 30) // run for 30 seconds, then fail

Еслиу вас нет замыканий или указателей на функции, вам придется делать это далеко и везде:

stopwatch = new Stopwatch(30)
stopwatch.start()
while stopwatch.elapsedTimeInSeconds() < 30
    hmph()
raise OperationTimedOutError()
...