На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
! В разделе обсуждаются следующие темы:
1) Процесс разработки программного обеспечения.
2) Определение требований к программному обеспечению.
3) Составные части и процесс проектирования (см. Шаблоны проектирования).
4) Документирование программного продукта(проекта).
5) Руководство разработкой программного обеспечения.
6) Проектирование пользовательского интерфейса.
7) Контроль версий проекта (см. Управление версиями в Subversion, Стратегии использования svn).
Модераторы: ElcnU
  
> Незасорение кода логгингом/трассировкой
    Любезный читатель, вероятно, уже имел возможность удивиться от моего предыдушего вопроса, но, меж тем, я не устал.

    Есть два взаимоисключающих желания: подробные логи и чистая, незамутнённая реализация логики в некоторой абстрактной программе. Хочется именно логов, а не трассировки: не последовательности вызовов методов и их аргументов, а нечто чуть более осмысленное, позволяющее понять что происходит и почему. Фейлы многих операций, в зависимости от контекста, могут быть как реальными фейлами, которые fatal error, так и чем-то вполне ожидаемым, поэтому хочется видеть в логах контекст. Причём не где-то на 200 записей выше, а непосредственно там, где произошло нечто, заслуживающее внимания.

    Крутятся мысли типа
    ExpandedWrap disabled
      interface IExecutionContext
      {
        string DescribeYourself { get; } // "читаем файл, которого вполне может не быть там где нужно"
        void Execute(IAction action); // выполняем действие в контексте "читаем файл, которого вполне может не быть там где нужно"
      }
       
      interface IAction // очень красивая абстракция
      {
        void Execute();
      }
       
      class ReadFileAction : IAction // читаем файл, кидаем эксепшн, если не можем
      {
        public ReadFileAction(string fileToRead) { ... }
        public void Execute() { ... }
      }
      ...
      IReadFileAction readFileAction = new ReadFileAction("1.txt");
      IExecutionContext iDontReallyCareIfItFailsOrNotExecutionContext = new IDontReallyCareIfItFailsOrNotExecutionContext("читаем файл, которого вполне может и не быть там где нужно");
      iDontReallyCareIfItFailsOrNotExecutionContext.Execute(readFileAction); // исключения нет, т.к. мы относимся нормально к тому, что файла нет
       
      // в логах при этом легко можно получить такой текст, если не удалось прочитать файл:
      // читаем файл, которого вполне может не быть там где нужно: нету такого файла - "1.txt"
       
      // а если удалось:
      // читаем файл, которого вполне может не быть там где нужно: прочитали


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

    Спасибо.
      Цитата andyag @
      IDontReallyCareIfItFailsOrNotExecutionContext
      :wacko: Избегай использования таких имён более 9 раз на проект :lol:
      Паттерн Command, он же экшн - это палка о двух концах: сколько этих экшнов ты готов наплодить? Как потом в них разбираться? А если незначительная модификация поведения нужна?..
      Года три назад я ударился в команды, соорудил целое дерево, всё красиво и законченно... На схеме... А в коде - 50 классов и 100+ интерфейсов, которые нужны этим классам только для того, чтобы дёрнуть один единственный метод... Либо я неправильно понял, либо одно из двух. "Фшопу!" сказал я и почесав репу переделал это, оставив от силы 7 команд и интерфейсов. Помог мне в этом boost::bind. Получились вполне себе говорящие такие конструкции, в которых во-первых видна последовательность действий, во-вторых - никто не обязан рожать и поддерживать какие-то там интерфейсы, в-третьих - легко модифицировать. Развив эту тему можно как раз прикрутить твои контексты, коих тоже будет невеликое, как я понимаю, множество...
        Цитата ss @
        Года три назад
        Года 3 назад в C++ не было этих. Анонимных делегатов чтоли? Сейчас-то никто не мешает добиться вот такого:
        ExpandedWrap disabled
          ...
          Context const context(outerContext.GetNestedContext("хотим прочитать из файла данные в формате CSV"));
          // т.е. если outerContext у нас был "импортируем данные из файла в базу", то context получится
          // "импортируем данные из файла в базу -> хотим прочитать из файла данные в формате CSV"
           
          Action const action(
            "читаем файл",
            [] { // обычный плюсовый код
              readFileHere();
            }
          );
          context.Execute(action);
          // "импортируем данные из файла в базу -> хотим прочитать из файла данные в формате CSV -> читаем файл"
          ...

        Такой блок по сути выглядит как комментарий и кусок кода после него. По-моему, сложность приемлемая, тем более, что константная. В дотнете (у меня в нём такая задача возникла) анонимные делегаты и лямбды есть уже давно, никого не удивишь.

        Это что-то меняет? :-)
          Цитата andyag @
          Это что-то меняет? :-)
          Ну хотя бы то, что у тебя не будет 100500 мелких запутанных классов.
          Цитата andyag @
          Анонимных делегатов чтоли?
          Эта холера умеет вызывать методы классов? Если нет - фукака. bind умеет, чем весьма полезен.
          Цитата andyag @
          GetNestedContext
          Неплохая идея, что-то в этом есть. Но можно реализовать иначе - просто завести stack контекстов.

          Что теперь нужно сделать: цепочки команд, этот твой composite action, в которых либо явно указывать вход во вложенный контекст и выход из него (что не очень удобно и красиво), либо ...пока не придумал, но всегда есть второй вариант :D

          Добавлено
          Цитата ss @
          Эта холера умеет вызывать методы классов? Если нет - фукака. bind умеет, чем весьма полезен.
          Почитал (наконец-то) про лямбды в C++0x - годно, надо брать. Умеет, вобщем.
            Чтобы быть более предметным, покажу вот такое:
            ExpandedWrap disabled
                  ...
                  sealed class FileReader
                  {
                      private readonly string _fileName;
               
                      public FileReader(string fileName)
                      {
                          _fileName = fileName;
                      }
               
                      public string GetData(ExecutionContext context)
                      {
                          var fileExists = context.Execute(
                              string.Format("checking if {0} exists", _fileName), // вот тут немного не айс
                              ctx => File.Exists(_fileName));
               
                          if(!fileExists)
                          {
                              throw new FileNotFoundException();
                          }
               
                          var data = context.Execute(
                              string.Format("reading data from {0}", _fileName), // и тут тоже немного не айс
                              delegate { // это можно через лямбду переписать, но можно и не переписывать
                                  return File.ReadAllText(_fileName);
                              });
               
                          return data;
                      }
               
                      public override string ToString()
                      {
                          return string.Format("FileReader(file={0})", _fileName);
                      }
                  }
                  ...
                  sealed class FileProcessor
                  {
                      private readonly FileReader _fileReader;
                      private readonly DataProcessor _dataProcessor;
               
                      public FileProcessor(FileReader fileReader, DataProcessor dataProcessor)
                      {
                          _fileReader = fileReader;
                          _dataProcessor = dataProcessor;
                      }
               
                      public void Process(ExecutionContext context)
                      {
                          var data = context.Execute("reading data", ctx => _fileReader.GetData(ctx));
                          context.Execute("processing data read", ctx => _dataProcessor.Process(ctx, data));
                      }
               
                      public override string ToString()
                      {
                          return string.Format(
                              "FileProcessor(reader={0},processor={1})",
                              _fileReader,
                              _dataProcessor);
                      }
                  }
                      ...
                      static void Main()
                      {
                          try
                          {
                              var context = new ExecutionContext("root");
               
                              // var fileReader = new FileReader("1.txt")
                              var fileReader = context.Execute("creating file reader", ctx => new FileReader("1.txt"));
               
                              // var dataProcessor = new DataProcessor()
                              var dataProcessor = context.Execute("creating data processor", ctx => new DataProcessor());
               
                              // var fileProcessor = new FileProcessor(fileReader, dataProcessor)
                              var fileProcessor =
                                  context.Execute(
                                  "creating file processor",
                                  ctx => new FileProcessor(fileReader, dataProcessor));
                              
                              // fileProcessor.Process()
                              context.Execute("running file processor", ctx => fileProcessor.Process(ctx));
                          }
                          catch(Exception ex)
                          {
                              Console.WriteLine("Fatal error: {0}", ex.Message);
                          }
                      }

            В случае, когда 1.txt нету, лог получается такой:
            ExpandedWrap disabled
              /creating file reader - succeeded [FileReader(file=1.txt)]
              /creating data processor - succeeded [DataProcessor]
              /creating file processor - succeeded [FileProcessor(reader=FileReader(file=1.txt),processor=DataProcessor)]
              /running file processor/reading data/checking if 1.txt exists - succeeded [False]
              /running file processor/reading data - failed: Unable to find the specified file.
              /running file processor - failed: Unable to find the specified file.
              Fatal error: Unable to find the specified file.


            Лог мне реально очень нравится. Я хочу именно такой лог.
            Вот плюсы:
            1. Если я хочу все исключения записывать более развёрнуто (например, все вложенные исключения выводить), я это легко сделаю добавив 3 строчки кода в одном единственном месте.
            2. Если всё писать единообразно - все ключевые выводы заворачивать в вызовы через контекст, в логе реально будет ВСЁ, что происходило.
            3. Если где-то произошла какая-то ошибка, легко определить ГДЕ именно и при каких обстоятельствах она произошла

            Минусы:
            1. Выглядит более массивно, чем простой код без обработки исключений. Ну, как бы, если их не обрабатывать, ничего хорошего не выйдет - всё равно нужно обрабатывать.
            2. Вся BL будет завязана на этот самый ExecutionContext.
            3. Ещё?
            Сообщение отредактировано: andyag -
              ИМХО PostSharp даст ответ на вопрос.
                Цитата deil @
                ИМХО PostSharp даст ответ на вопрос.
                Расскажи подробнее, возможно я его слишком узколобо понимаю. Как с помощью него добиться описания контекста? Простейший пример, есть у тебя функция sin(double x), есть какая-то процедура proc(), которая использует ту sin() 2 раза в разных ситуациях. Как я понимаю, PostSharp поможет описать контекст sin в базисе самого sin(), т.е. можно сделать, чтобы оно говорило "вычисляем синус для xxx". Как мне дополнить этот контекст контекстом вызова? ("вычисляем матрицу ДКП - вычисляем синус", "вычисляем какую-то там ещё матрицу - вычисляем синус").
                  PostSharp реализует AOP - он на этапе компиляции встраивает в вызов метода свои callback'и, которые можно дёрнуть до, после, по-моему при возникновении эксепшна и т.д.
                  Сам по себе он не решает твою задачу, но разве нельзя например на sin(x) навесить PreExecute экшн, в нем поднять стек вызовов и залогить, откуда конкретно и по каким параметрам мы вызвали метод? По имени вызывающего метода и догадаться =)
                  Ну либо на вызывающий метод навесить кастомный атрибут с описанием контекста (ну для начала просто string description), из PreExecute его прочитать и отлогить. Как-нибудь так..
                    Цитата deil @
                    поднять стек вызовов
                    Я как-то виде код, логика которого зависела от стека вызовов в явном виде. Прям смотрелся стек и дальше - иф. По-моему плохая идея.
                      Ну ты в крайности не ударяйся. Подсмотреть, кто тебя вызвал и взять его метаданные - это безобидно
                        Цитата deil @
                        это безобидно
                        С этим согласиться не могу. Одно дело когда у тебя что-то крешится и ты хочешь максимум контекста собрать - безобидно. Другое дело когда у тебя часть функциональности на это завязана. ИМХО, слишком неявно и неожиданно.
                          К тому же для обеспечения той же функциональности обычно есть более явные и эффективные способы.
                          А вот в целях отладки или журналирования (опять же в целях отладки) вполне можно и стек дергать.
                            Не, ну вы покажите более явные и эффективные способы и обсудим :)
                              Основной - для каждого действия иметь конкретную процедуру/метод.
                              В данном случае нужен лог, если в него заносится действия в конкретной процедуре, то можно и стек вызовов дергать, если там информации достаточно. С другой стороны, если протоколирование действий - часть нормального функционирования программы, то лучше, чтобы оно тоже в ее логике отражалось.
                              0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                              0 пользователей:


                              Рейтинг@Mail.ru
                              [ Script execution time: 0,0412 ]   [ 16 queries used ]   [ Generated: 28.03.24, 12:09 GMT ]