Сообщение информации во время выполнения кода: лучший дизайн - PullRequest
6 голосов
/ 12 апреля 2009

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

Скажем, у вас есть следующий (глупый, чтобы быть простым) случай. Я буду использовать Python.

def doStuff():
    doStep1()
    doStep2()
    doStep3()

Теперь предположим, что вы хотите сообщить о различных шагах, если что-то идет не так и т. Д. Не совсем отладка: просто информативное поведение приложения.

Первое, простое решение - нанести отпечатки

def doStuff():
    print "starting doing stuff"
    print "I am starting to do step 1"
    doStep1()
    print "I did step 1"
    print "I am starting to do step 2"
    doStep2()
    print "I did step 2"
    print "I am starting to do step 3"
    doStep3()
    print "I did step 3"

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

Другим решением является наличие модуля для регистрации.

def doStuff():
    Logging.log("starting doing stuff")
    Logging.log("I am starting to do step 1")
    doStep1()
    Logging.log("I did step 1")
    Logging.log("I am starting to do step 2")
    doStep2()
    Logging.log("I did step 2")
    Logging.log("I am starting to do step 3")
    doStep3()
    Logging.log("I did step 3")

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

Третий вариант - иметь объект отчета с понятным интерфейсом, и вы передаете его

def doStuff(reporter=NullReporter()):
    reporter.log("starting doing stuff")
    reporter.log("I am starting to do step 1")
    doStep1()
    reporter.log("I did step 1")
    reporter.log("I am starting to do step 2")
    doStep2()
    reporter.log("I did step 2")
    reporter.log("I am starting to do step 3")
    doStep3()
    reporter.log("I did step 3")

В конце концов, вы также можете передать объект-репортер в doStepX (), если им есть что сказать. Преимущество: он уменьшает связь с модулем, но вводит связь с созданием объекта NullReporter. Это можно решить, используя None по умолчанию и проверяя перед вызовом log, что неуклюже, потому что в python вы должны писать условные выражения каждый раз (в C вы можете определить макрос)

def doStuff(reporter=None):
    if reporter is not None:
        reporter.log("starting doing stuff")
        # etc...

Edit: Другой вариант - работать в стиле Qt и иметь стратегию сигнала emit (). Когда ваш код выполняется, он генерирует информацию с соответствующими кодами состояния, и любой желающий может подписаться на сигналы и предоставить информацию. Хороший и чистый, очень отделенный, но требует немного кодирования, так как я не думаю, что это можно быстро сделать с включенной батареей питона.

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

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

 if disconnected:
     print "Trying to connect"
     connect()
 else:
     print "obtaining list of files from remote host"
     getRemoteList()

Отчет также может быть в реальных подпрограммах, так что в качестве подпрограммы connect () и getRemoteList () в качестве первого оператора вы можете использовать «print».

Таким образом, вопрос таков:

  • Как вы думаете, что является лучшим дизайном для некоторого кода (особенно в случае библиотеки), который должен быть в то же время тихим, когда шум может быть разрушительным для клиента, но многословным, когда полезно?
  • Как обрабатывать сбалансированное смешивание между логическим кодом и кодом отчетности?
  • Смешивание между проверкой кода и ошибок было решено с исключениями. Что можно сделать, чтобы отделить «шум» отчетности от логики кода?

Редактировать: больше мыслей для ума

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


Редактировать: Я принял ответ от Марсело, потому что он указывает на фактические доказательства того, что компромисс является лучшим решением в этом случае, и нет серебряной пули. Тем не менее, все остальные тоже были интересными ответами, и мне было очень приятно отозвать их все. Спасибо за помощь!

Ответы [ 9 ]

4 голосов
/ 12 апреля 2009

Я думаю, что лучшее решение для библиотеки - это одно из таких добавлений, как, например,

Log.Write(...)

, где поведение Log берется из окружающей среды (например, app.config или переменная окружения).

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

Я не вижу хорошего способа «отделить» «нормальную» часть вашего кода от части «регистрация». Ведение журнала имеет тенденцию быть относительно ненавязчивым; Я не считаю, что случайный Log.Write (...) отвлекает от реального кода.

2 голосов
/ 14 апреля 2009

Я часто использую DTrace для этого. В OS X python и ruby ​​уже настроены с использованием ловушек DTrace. На других платформах вам, вероятно, придется делать это самостоятельно. Но способность прикреплять отладочные трассы к запущенному процессу, ну, в общем, потрясающе.

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

2 голосов
/ 12 апреля 2009

Я нашел это при поиске Aspect Oriented Programming для python. Я согласен с другими авторами, что такие проблемы не должны смешиваться с основной логикой.

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

2 голосов
/ 12 апреля 2009

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

Это, вероятно, не стоит дополнительной сложности, хотя ...

1 голос
/ 16 апреля 2009

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

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

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

Вы также можете поместить его в другой поток и перехватывать события журнала a la Qt.

1 голос
/ 14 апреля 2009

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

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

1 голос
/ 14 апреля 2009

Я думаю, что самое простое решение - лучшее здесь. Это зависит от языка, но просто используйте очень короткий, глобально доступный идентификатор - в PHP я использую пользовательскую функцию trace($msg) - а затем просто реализую и повторно реализую этот код так, как вы считаете подходящим для конкретного проекта или фазы.

Автоматическая версия этого компилятора является стандартным отладчиком. Если вы хотите видеть значимые ярлыки, вам, к сожалению, нужно самим писать эти ярлыки:)

Или вы можете попытаться временно преобразовать встроенные комментарии в вызовы функций, но я не уверен, что это сработает.

1 голос
/ 14 апреля 2009

Я бы использовал стандартный logging модуль, который был частью стандартной библиотеки начиная с Python 2.3.

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

Есть ли какая-либо функция, которую вы хотите, но не можете найти в стандартном модуле logging?

1 голос
/ 12 апреля 2009

Должен быть инструментарий, позволяющий автоматически (и выборочно) генерировать сообщения журнала шаблонов а-ля "ввод метода A с параметрами (1,2,3)", "возвращение из метода B со значением X, заняло 10 мс" (контролируется во время выполнения или развертывания). Написание этого материала вручную слишком скучно / повторяющееся / подвержено ошибкам.

Не уверен, что есть, хотя.

Если вы собираетесь писать сообщения журнала вручную, обязательно включите некоторую полезную контекстную информацию (идентификатор пользователя, просматриваемый URL-адрес, поисковый запрос и т. Д.), Чтобы, если что-то пойдет не так, вы получили больше информации чем просто имя метода.

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