Подробные журналы учета и чистый код (не АОП)


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

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

Когда происходит что-то действительно интересное, я хочу увидеть его сразу, без прокрутки вверх и вниз по моей 500кб файл журнала и пытаясь вспомнить код.

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

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 { // can easily use lambdas and anonymous delegates
                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);
        }
    }

Результаты этого кода:

/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.

Мне очень нравится результат, но я не уверен, если оно того стоит. Любые идеи?



1180
7
c#
задан 19 июля 2011 в 06:07 Источник Поделиться
Комментарии
3 ответа

Для чего это стоит после всего этого времени ты задал вопрос:


Стоит ли оно того?

Ответ: Нет.

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

public string GetData(ExecutionContext context)
{
if(!File.Exists(_fileName))
{
throw new FileNotFoundException();
}
return File.ReadAllText(_fileName);
}

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

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

Вы также столкнуться с той же проблемой, как и вы с комментируя каждое утверждение: Вы дважды пишете код. Как только для компилятора выполнить и один раз для разработчика/лог-файл для чтения/записи. Правда с ведения подробного журнала можно нарваться на подобные вопросы.

Вы также сказали, что вы не хотите трассировки, а на самом деле, по крайней мере, ваш пример кода сводится к тому, что именно. Таким образом, используя в рамках АОП может быть лучшей альтернативой в вашем случае.

12
ответ дан 31 января 2014 в 08:01 Источник Поделиться

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

Вы можете просто сделать

log.DebugFormat("creating file reader for file {0}", fileName);
log.Error("Unable to find the specified file");

И в зависимости от конфигурации можно указать, это может быть выход, как

Debug - 15:04:00.657 - (ThreadName) - FileReader - creating file reader for file foobar.txt
Error - 15:04:02.542 - (ThreadName) - FileReader - Unable to find the specified file

и т. д.

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

Вы также можете настроить, какие сообщения вам на выходе все путем указания общего уровня вход из внешнего XML-файла. Например, в серийной версии будет установлен на "инфо" в то время как версия Дев будет на "отладки".

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

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

5
ответ дан 10 сентября 2012 в 01:09 Источник Поделиться

Альтернатива прибегая к АОП, можно извлечь IFileReader интерфейс, и украсить на FileReader с некоторыми LoggingEnabledFileReader - обратите внимание, что герметичный явно мешает ваши кода, расширяемый, я бы его бросила (ООН-запечатывание класса не должны быть критические изменения). Это решение является немного слиянием двух других ответов.

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

public interface IFileReader
{
string GetData(string fileName);
}

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

public class FileReader : IFileReader
{
public string GetData(string fileName)
{
return File.ReadAllText(fileName);
}
}

Затем вы реализуете декоратор - мне нравится NLog, и с Ninject.Расширения.Войдя я конструктор-ввести регистратор в любом классе, даже не задумываясь об этом:

public class LoggingEnabledFileReader : IFileReader
{
private readonly IFileReader _reader;
private readonly ILogger _logger;

public LoggingEnabledFileReader(IFileReader reader, ILogger logger)
{
_reader = reader;
_logger = logger;
}

public string GetData(string fileName)
{
string result;
try
{
_logger.Trace(string.Format("Reading data from '{0}'...", fileName));
result = _reader.GetData(fileName);
}
catch(Exception ex)
{
// we only need a warning-level log entry here
_logger.WarnException(string.Format("Error with file '{0}'.", fileName), ex);
}

return result;
}
}

Таким образом у вас есть очень простой FileReader класс, и очень простой LoggingEnabledFileReader класс, как реализуя ту же IFileReader интерфейс. Если FileProcessor взял абстракция как зависимость, а не конкретные реализации...

private readonly IFileReader _fileReader;

public FileProcessor(IFileReader fileReader /*, ... */)

...тогда, когда FileProcessor звонки _fileReader.Метода GetData, он не знает ли читатель журнала-включен или нет, и это не заботит.

В FileReader код так же просто, как он получает, и LoggingEnabledFileReader код не больше и не меньше, чем то, что он говорит это; ведение журнала концерна заботятся явно лесозаготовки-соответствующего класса.


В параллельном режиме материал выглядит более проработанная, более сложные и в целом более-ТОП за борт с чрезмерного использования лямбда-выражений и делегатов и анонимных методов.

Поцелуй - держите это простым, глупым.

3
ответ дан 27 марта 2014 в 01:03 Источник Поделиться