Шаблон для выдачи ошибок / предупреждений при разборе в библиотеке - PullRequest
4 голосов
/ 25 октября 2010

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

// Rough outline of the current code
public abstract class ModelReader : IDisposable
{
    public abstract Model Read();

    public event EventHandler<MessageAvailableEventArgs> MessageAvailable;

    protected virtual void RaiseError(string message)
    {
        var handler = this.MessageAvailable;
        if (handler != null)
        {
            handler(this, new MessageAvailaibleEventArgs(
                TraceEventType.Error, message);
        }
    }
}

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

Ответы [ 6 ]

3 голосов
/ 28 сентября 2011

Я могу привести пример из реальной жизни. Библиотека Html Agility Pack является библиотекой синтаксического анализа. Он просто определяет список ошибок синтаксического анализа в классе читателя. Расширяя ваш пример, это будет что-то вроде:

    public abstract class ModelReader
    {
        private List<ParseError> _errors = new List<ParseError>();
        private bool _throwOnError;

        public ModelReader()
           :this(true)
        {
        }

        public ModelReader(bool throwOnError)
        {
           _throwOnError = throwOnError;
        }

        // use AddError in implementation when an error is detected
        public abstract Model Read();

        public virtual IEnumerable<ParseError> Errors
        {
           get {return _errors;}
        }

        protected virtual void AddError(ParseError error)
        {
           if (_throwOnError) // fail fast?
              throw new ParseException(error);

           _errors.Add(error);
        }
    }

    public class ParseError
    {
        public ParseError(...)
        {
        }

        public ParseErrorCode Code { get; private set; }
        public int Line { get; private set; }
        public int LinePosition { get; private set; }
        public string Reason { get; private set; }
        public string SourceText { get; private set; }
        public int StreamPosition { get; private set; }
    }

    public enum ParseErrorCode
    {
       InvalidSyntax,
       ClosingQuoteNotFound,
       ... whatever...
    }

    public class ParseException: Exception
    {
        ...
    }

И вы все еще можете добавить событие, если вызывающий библиотеку хочет события на лету.

2 голосов
/ 02 октября 2011

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

  1. Зарегистрируйте столько обработчиков исключений, сколько пожелаете
  2. При обнаружении проблемы на проходе все обработчики спрашиваются (пока не выбрасывается исключение!), Какой из них хочет обработать ошибку. Первый, который говорит «да», станет фактическим обработчиком, который решает, что делать.
  3. Когда был найден обработчик, который может обработать, мы переходим к этапу 2 и вызываем его. На этот раз это время исключения или нет. Это полностью зависит от обработчика.

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

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

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

Библиотеки Windows SDK иногда довольно сложны в использовании, потому что инженеры там оптимизируют больше для меньшего количества обращений за обслуживанием. Они выдают вам код ошибки Win32 или HResult, и вы должны выяснить, какой принцип (выравнивание памяти, размер буфера, кросс-потоки, ...) вы нарушили.

1 голос
/ 29 сентября 2011

Ваш текущий подход не является самой чистой моделью, потому что он имеет два противоречивых стиля выполнения: pull (чтение ввода) и push (обработка обратных вызовов с ошибками), и это означает, что и вашему читателю, и его клиентам требуется больше управления состоянием, чтобы обеспечитьсвязное целое.

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

1 голос
/ 26 октября 2010

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

public interface IMessageHandler
{
    void HandleMessage(object sender, MessageAvailaibleEventArgs eventArgs);
}    

public abstract class ModelReader : IDisposable
{
    private readonly IMessageHandler handler; // Should be initialized somewhere, e.g. in constructor

    public abstract Model Read();

    public event EventHandler<MessageAvailableEventArgs> MessageAvailable;

    protected virtual void RaiseError(string message)
    {
        MessageAvailaibleEventArgs eventArgs =
            new MessageAvailaibleEventArgs(TraceEventType.Error, message);
        this.handler.HandleMessage(this, eventArgs);
    }
}

Так что теперь вы можете использовать любую реализацию обработчика сообщений, например, подписку на событие:

public class EventMessageHandler : IMessageHandler
{
    public event EventHandler<MessageAvailaibleEventArgs> MessageAvailable;

    public void HandleMessage(object sender, MessageAvailaibleEventArgs eventArgs)
    {
        var handler = this.MessageAvailable;
        if (handler != null)
        {
            handler(this, new MessageAvailaibleEventArgs(
                TraceEventType.Error, message);
        }
    }
}
0 голосов
/ 03 октября 2011

Я знаю, что вы упомянули, что у вас может быть несколько подписчиков, поэтому обработчики событий и интерфейсный вызов Injected являются хорошими решениями, как уже упоминалось выше.Для полноты я также предложу предоставить необязательный параметр Read () в качестве Func.например:

    void Read(Func<string, bool> WarningHandler)
    {
        bool cancel = false;
        if (HasAWarning)
            cancel = WarningHandler("Warning!");
    }

Тогда, конечно, вы можете делать все, что хотите в делегате Func, например, распространять предупреждение для нескольких источников.Но в сочетании с моделью событий вы можете обрабатывать все предупреждения в общем виде (например, в логгере) и использовать Func для отдельных специализированных действий и потока управления (при необходимости).

0 голосов
/ 26 октября 2010

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

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

 public TextWriter Log { get; set; }

 private void WriteToLog(string Message)
 {
    if (Log != null) Log.WriteLine(message);
 }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...