Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[3.138.33.178] |
|
Сообщ.
#1
,
|
|
|
Любезный читатель, вероятно, уже имел возможность удивиться от моего предыдушего вопроса, но, меж тем, я не устал.
Есть два взаимоисключающих желания: подробные логи и чистая, незамутнённая реализация логики в некоторой абстрактной программе. Хочется именно логов, а не трассировки: не последовательности вызовов методов и их аргументов, а нечто чуть более осмысленное, позволяющее понять что происходит и почему. Фейлы многих операций, в зависимости от контекста, могут быть как реальными фейлами, которые fatal error, так и чем-то вполне ожидаемым, поэтому хочется видеть в логах контекст. Причём не где-то на 200 записей выше, а непосредственно там, где произошло нечто, заслуживающее внимания. Крутятся мысли типа 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 снабдить собственным контекстом. Развивая ещё дальше - реализовать вложенные контексты, и т.д. Поругайте-похвалите мысль. Встречался ли кто с такими решениями? Насколько это страшно при сопровождении, при появлении нового человека в проекте? Спасибо. |
Сообщ.
#2
,
|
|
|
Цитата andyag @ Избегай использования таких имён более 9 раз на проект IDontReallyCareIfItFailsOrNotExecutionContext Паттерн Command, он же экшн - это палка о двух концах: сколько этих экшнов ты готов наплодить? Как потом в них разбираться? А если незначительная модификация поведения нужна?.. Года три назад я ударился в команды, соорудил целое дерево, всё красиво и законченно... На схеме... А в коде - 50 классов и 100+ интерфейсов, которые нужны этим классам только для того, чтобы дёрнуть один единственный метод... Либо я неправильно понял, либо одно из двух. "Фшопу!" сказал я и почесав репу переделал это, оставив от силы 7 команд и интерфейсов. Помог мне в этом boost::bind. Получились вполне себе говорящие такие конструкции, в которых во-первых видна последовательность действий, во-вторых - никто не обязан рожать и поддерживать какие-то там интерфейсы, в-третьих - легко модифицировать. Развив эту тему можно как раз прикрутить твои контексты, коих тоже будет невеликое, как я понимаю, множество... |
Сообщ.
#3
,
|
|
|
Цитата ss @ Года 3 назад в C++ не было этих. Анонимных делегатов чтоли? Сейчас-то никто не мешает добиться вот такого:Года три назад ... Context const context(outerContext.GetNestedContext("хотим прочитать из файла данные в формате CSV")); // т.е. если outerContext у нас был "импортируем данные из файла в базу", то context получится // "импортируем данные из файла в базу -> хотим прочитать из файла данные в формате CSV" Action const action( "читаем файл", [] { // обычный плюсовый код readFileHere(); } ); context.Execute(action); // "импортируем данные из файла в базу -> хотим прочитать из файла данные в формате CSV -> читаем файл" ... Такой блок по сути выглядит как комментарий и кусок кода после него. По-моему, сложность приемлемая, тем более, что константная. В дотнете (у меня в нём такая задача возникла) анонимные делегаты и лямбды есть уже давно, никого не удивишь. Это что-то меняет? :-) |
Сообщ.
#4
,
|
|
|
Цитата andyag @ Ну хотя бы то, что у тебя не будет 100500 мелких запутанных классов.Это что-то меняет? :-) Цитата andyag @ Эта холера умеет вызывать методы классов? Если нет - фукака. bind умеет, чем весьма полезен.Анонимных делегатов чтоли? Цитата andyag @ Неплохая идея, что-то в этом есть. Но можно реализовать иначе - просто завести stack контекстов.GetNestedContext Что теперь нужно сделать: цепочки команд, этот твой composite action, в которых либо явно указывать вход во вложенный контекст и выход из него (что не очень удобно и красиво), либо ...пока не придумал, но всегда есть второй вариант Добавлено Цитата ss @ Почитал (наконец-то) про лямбды в C++0x - годно, надо брать. Умеет, вобщем. Эта холера умеет вызывать методы классов? Если нет - фукака. bind умеет, чем весьма полезен. |
Сообщ.
#5
,
|
|
|
Чтобы быть более предметным, покажу вот такое:
... 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 нету, лог получается такой: /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. Ещё? |
Сообщ.
#6
,
|
|
|
ИМХО PostSharp даст ответ на вопрос.
|
Сообщ.
#7
,
|
|
|
Цитата deil @ Расскажи подробнее, возможно я его слишком узколобо понимаю. Как с помощью него добиться описания контекста? Простейший пример, есть у тебя функция sin(double x), есть какая-то процедура proc(), которая использует ту sin() 2 раза в разных ситуациях. Как я понимаю, PostSharp поможет описать контекст sin в базисе самого sin(), т.е. можно сделать, чтобы оно говорило "вычисляем синус для xxx". Как мне дополнить этот контекст контекстом вызова? ("вычисляем матрицу ДКП - вычисляем синус", "вычисляем какую-то там ещё матрицу - вычисляем синус"). ИМХО PostSharp даст ответ на вопрос. |
Сообщ.
#8
,
|
|
|
PostSharp реализует AOP - он на этапе компиляции встраивает в вызов метода свои callback'и, которые можно дёрнуть до, после, по-моему при возникновении эксепшна и т.д.
Сам по себе он не решает твою задачу, но разве нельзя например на sin(x) навесить PreExecute экшн, в нем поднять стек вызовов и залогить, откуда конкретно и по каким параметрам мы вызвали метод? По имени вызывающего метода и догадаться =) Ну либо на вызывающий метод навесить кастомный атрибут с описанием контекста (ну для начала просто string description), из PreExecute его прочитать и отлогить. Как-нибудь так.. |
Сообщ.
#9
,
|
|
|
Цитата deil @ Я как-то виде код, логика которого зависела от стека вызовов в явном виде. Прям смотрелся стек и дальше - иф. По-моему плохая идея. поднять стек вызовов |
Сообщ.
#10
,
|
|
|
Ну ты в крайности не ударяйся. Подсмотреть, кто тебя вызвал и взять его метаданные - это безобидно
|
Сообщ.
#11
,
|
|
|
Цитата deil @ С этим согласиться не могу. Одно дело когда у тебя что-то крешится и ты хочешь максимум контекста собрать - безобидно. Другое дело когда у тебя часть функциональности на это завязана. ИМХО, слишком неявно и неожиданно. это безобидно |
Сообщ.
#12
,
|
|
|
К тому же для обеспечения той же функциональности обычно есть более явные и эффективные способы.
А вот в целях отладки или журналирования (опять же в целях отладки) вполне можно и стек дергать. |
Сообщ.
#13
,
|
|
|
Не, ну вы покажите более явные и эффективные способы и обсудим
|
Сообщ.
#14
,
|
|
|
Основной - для каждого действия иметь конкретную процедуру/метод.
В данном случае нужен лог, если в него заносится действия в конкретной процедуре, то можно и стек вызовов дергать, если там информации достаточно. С другой стороны, если протоколирование действий - часть нормального функционирования программы, то лучше, чтобы оно тоже в ее логике отражалось. |