Ошибки

AndrewVK AndrewVK
Задача: есть некий обработчик, для простоты пусть это будет компилятор. В процессе своей работы он может выдавать некоторое количество ошибок. Каждая ошибка характеризуется местом возникновения (файл, строка, колонка), уникальным символьным кодом и текстом с опциональными параметрами на нескольких языках. При этом текст должен выдаваться на языке, который выставлен у потока, выводящего ошибки, а не у потока компиляции (они могут не совпадать).
Вопрос — как сдизайнить прежде всего саму ошибку, чтобы максимально контролировать статически все аспекты при ее формировании.
Язык — C#.
... << RSDN@Home 1.2.0 alpha 5 rev. 65 on Windows 8 6.2.9200.0>>
IB
IB
21.09.2012 04:15
Здравствуйте, AndrewVK, Вы писали:

AVK>Вопрос — как сдизайнить прежде всего саму ошибку, чтобы максимально контролировать статически все аспекты при ее формировании.

Ну, насколько я понимаю, в данном контексте "ошибка" — это вполне валидный сценарий. То есть это должно быть не исключение, а просто класс, который в рантайме при создании получает соответствующий текст из соответствующего ресурса...
Или я не догнал сложность проблемы?
AndrewVK
AndrewVK
21.09.2012 05:02
Здравствуйте, IB, Вы писали:

IB>Ну, насколько я понимаю, в данном контексте "ошибка" — это вполне валидный сценарий. То есть это должно быть не исключение, а просто класс, который в рантайме при создании получает соответствующий текст из соответствующего ресурса...

IB>Или я не догнал сложность проблемы?

Да пофигу, исключение или класс. Проблема в другом — как сделать типизированным все без кучи рукопашной писанины.
... << RSDN@Home 1.2.0 alpha 5 rev. 65 on Windows 8 6.2.9200.0>>
WolfHound
WolfHound
22.09.2012 08:49
Здравствуйте, AndrewVK, Вы писали:

AVK>Да пофигу, исключение или класс. Проблема в другом — как сделать типизированным все без кучи рукопашной писанины.

А где ты видишь "кучу рукопашной писанины"?
... << RSDN@Home 1.2.0 alpha 5 rev. 62>>
AndrewVK
AndrewVK
22.09.2012 09:33
Здравствуйте, WolfHound, Вы писали:

AVK>>Да пофигу, исключение или класс. Проблема в другом — как сделать типизированным все без кучи рукопашной писанины.

WH>А где ты видишь "кучу рукопашной писанины"?

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

P.S. На всякий случай — я специально выделение добавил в стартовом сообщении.
... << RSDN@Home 1.2.0 alpha 5 rev. 65 on Windows 8 6.2.9200.0>>
WolfHound
WolfHound
22.09.2012 10:32
Здравствуйте, AndrewVK, Вы писали:

AVK>P.S. На всякий случай — я специально выделение добавил в стартовом сообщении.

Ну, раз ты настаиваешь на использовании слабого языка тогда можно пошаманить с рефлекшеном.
Перевод можно складывать в XML файлы. Их тоже можно элементарно проверять на вшивость пробежавшись рефлекшеном по свойствам.
public class ErrorDescriptor
{
  public string Name { get; private set; }
  public ErrorDescriptor(string name)
  {
    Name = name;
  }

  public Error New(string file, int line)
  {
    return new Error(this, file, line);
  }
}

public class Error
{
  public ErrorDescriptor Descriptor { get; private set; }
  public string File { get; private set; }
  public int Line { get; private set; }
  public Error(ErrorDescriptor descriptor, string file, int line)
  {
    Descriptor = descriptor;
    File = file;
    Line = line;
  }
}

public static class Errors
{
  public static ErrorDescriptor Error1 { get; private set; }
  public static ErrorDescriptor Error2 { get; private set; }
  public static ErrorDescriptor Error3 { get; private set; }
  public static ErrorDescriptor Error4 { get; private set; }

  static Errors()
  {
    foreach (var prop in typeof(Errors).GetProperties())
    {
      prop.SetValue(null, new ErrorDescriptor(prop.Name), null);
    }
  }
}


PS C# ужасен. Я себе после немерла даже на этих нескольких строках пальцы сломал.
... << RSDN@Home 1.2.0 alpha 5 rev. 62>>
AndrewVK
AndrewVK
22.09.2012 12:47
Здравствуйте, WolfHound, Вы писали:

WH>Ну, раз ты настаиваешь на использовании слабого языка тогда можно пошаманить с рефлекшеном.


То есть в ответ на вопрос о том, как сделать чтобы все максимально контролировалось компилятором, ты предлагаешь пошаманить с рефлекшеном?

WH>Перевод можно складывать в XML файлы.


Перевод нужно складывать в файлы resx.

WH>PS C# ужасен. Я себе после немерла даже на этих нескольких строках пальцы сломал.


Вопрос привычки.
... << RSDN@Home 1.2.0 alpha 5 rev. 65 on Windows 8 6.2.9200.0>>
WolfHound
WolfHound
22.09.2012 01:35
Здравствуйте, AndrewVK, Вы писали:

AVK>То есть в ответ на вопрос о том, как сделать чтобы все максимально контролировалось компилятором, ты предлагаешь пошаманить с рефлекшеном?

Ты код то смотрел?
Errors.Error1.New(file, line)
Куда уж больше контроля компилятором?
Можно конечно еще круче, но не на C#.
Если бы не требование "чисто C#" то все бы решалось простейшим макросом, который можно написать, не приходя в сознание.
А без генератора кода ничего умнее чем то, что я показал, ты не придумаешь.

WH>>Перевод можно складывать в XML файлы.

AVK>Перевод нужно складывать в файлы resx.
А давай ты сразу все требования озвучишь.

WH>>PS C# ужасен. Я себе после немерла даже на этих нескольких строках пальцы сломал.

AVK>Вопрос привычки.
Вопрос писанины. Её намного больше чем нужно.
Например, в немерле не нужно писать такие тупые конструкторы.
... << RSDN@Home 1.2.0 alpha 5 rev. 62>>
hardcase
hardcase
23.09.2012 10:07
Здравствуйте, AndrewVK, Вы писали:

AVK>Перевод нужно складывать в файлы resx.


С учетом совокупности требований:
1) язык C#
2) ресурсы в resx
3) максимальный статический контроль

Я сделал бы собственный процессор resx файлов, создающий обвязку в духе продемонстрированной WolfHound-ом. Вот этот класс:
public static class Errors
{
  public static ErrorDescriptor Error1 { get; private set; }
  public static ErrorDescriptor Error2 { get; private set; }
  public static ErrorDescriptor Error3 { get; private set; }
  public static ErrorDescriptor Error4 { get; private set; }

  static Errors()
  {
    foreach (var prop in typeof(Errors).GetProperties())
    {
      prop.SetValue(null, new ErrorDescriptor(prop.Name), null);
    }
  }
}

Сгенерировался бы по resx файлу означенной тулзой. В случае, если наше сообщение параметризовано (например текст в resx имеет формат подстановки string.Format), я бы генерировал статические методы с соответствующими параметрами.

Собственно кодогенерацию я бы выполнил с помощью NRefactory.

PS Да в Nemerle тожесамое сделал бы несложный макрос за значительно меньшее число знаков.

WH>>PS C# ужасен. Я себе после немерла даже на этих нескольких строках пальцы сломал.


AVK>Вопрос привычки.


В точку.
.
.
24.09.2012 10:22
Здравствуйте, AndrewVK, Вы писали:

AVK>Задача: есть некий обработчик, для простоты пусть это будет компилятор. В процессе своей работы он может выдавать некоторое количество ошибок. Каждая ошибка характеризуется местом возникновения (файл, строка, колонка), уникальным символьным кодом и текстом с опциональными параметрами на нескольких языках. При этом текст должен выдаваться на языке, который выставлен у потока, выводящего ошибки, а не у потока компиляции (они могут не совпадать).

AVK>Вопрос — как сдизайнить прежде всего саму ошибку, чтобы максимально контролировать статически все аспекты при ее формировании.
AVK>Язык — C#.
На яве я бы сделал так:
class Position
{
  File file;
  int line, col;
}

interface CompileProblems
{
  void error1(Position pos, String symbol, String param1);
  void error2(Position pos, int number);
  void warn42(Position pos);
}
...
interface SyntaxProblems
{
  void error55(Position pos, String symbol, String param1);
  void error204(Position pos, int number);
}

А имплементацию бы создавал либо динамическим proxy либо кодогенерацией во время сборки.
По полному имени метода можно извлекать соответствующий ресурс из bundle в зависимости от языка.
В юзерский код имплементация вставляется DI с помощью IoC Container.
Удобно тестировать — элементарно писать моки и expectation вызова конкретного метода, чем парсить текстовый вывод ошибок.
AndrewVK
AndrewVK
24.09.2012 12:14
Здравствуйте, ., Вы писали:

.> void error1(Position pos, String symbol, String param1);

.> void error2(Position pos, int number);
.> void warn42(Position pos);

А читать вызывающий код как потом? warn42 как то не очень дескриптивен.

.>Удобно тестировать — элементарно писать моки и expectation вызова конкретного метода, чем парсить текстовый вывод ошибок.


Чтобы не парсить текст ошибки как раз символьный errorcode и нужен
... << RSDN@Home 1.2.0 alpha 5 rev. 65 on Windows 8 6.2.9200.0>>
.
.
24.09.2012 05:48
Здравствуйте, AndrewVK, Вы писали:

.>> void error1(Position pos, String symbol, String param1);

.>> void error2(Position pos, int number);
.>> void warn42(Position pos);
AVK>А читать вызывающий код как потом? warn42 как то не очень дескриптивен.
Если тупой javadoc (или чего там такое в c#?) не устроит, но можно сделать соглашение именования, типа warn42_shortDescription.

.>>Удобно тестировать — элементарно писать моки и expectation вызова конкретного метода, чем парсить текстовый вывод ошибок.

AVK>Чтобы не парсить текст ошибки как раз символьный errorcode и нужен
Мне кажется, что для тестов часто нужен не только код, но и параметры. Например, "E1234: symobol 'aaa' not found" нужно же убедиться, что именно aaa не найден.
hardcase
hardcase
24.09.2012 08:08
Здравствуйте, ., Вы писали:

.>Мне кажется, что для тестов часто нужен не только код, но и параметры. Например, "E1234: symobol 'aaa' not found" нужно же убедиться, что именно aaa не найден.


Но это уже будут не юнит-тесты. Вот пример тестов таких сообщений.
.
.
24.09.2012 08:32
Здравствуйте, hardcase, Вы писали:

.>>Мне кажется, что для тестов часто нужен не только код, но и параметры. Например, "E1234: symobol 'aaa' not found" нужно же убедиться, что именно aaa не найден.


H>Но это уже будут не юнит-тесты. Вот пример тестов таких сообщений.

Не понял почему.
Примерно так на яве будет:
class CompilerTest
{
 @Mock CompileProblems problems;

 Compiler compiler; 

 @SetUp
 void setUp()
 {
   compiler = new Compiler(...);
   compiler.setCompileProblems(problems);
   ...
 }

 @Test
 public void testError1234()
 {
    compiler.compileString("var v = aaa;");

    verify(problems).error1234_symbolNotFound(any(), "aaa");
 }
 ...
 @Test
 public void testBigFile()
 {
    File file = new File("bigfile.n");
    compiler.compileFile(file);

    verify(problems).error4321_attrNotValidOnIface(any(), "Conditional");
    ...
    verify(problems).error4321_attrNotValidForNonVoidMethod(new Position(file, 25, 4), "Conditional", "X() : int");
    ...
 }
}

И никаких магических регекспов.
hardcase
hardcase
24.09.2012 08:56
Здравствуйте, ., Вы писали:

.>И никаких магических регекспов.


Все не так просто, "магические регекспы" помимо шаблона сообщения несут в себе еще и номер строки. Приведенный пример — это негативный тест, т.е. файл, компиляция которого завершается с ошибкой. Есть еще и позитивные тесты — они компилируются без ошибки (но возможно с предупреждениями, которые нужно аннотировать в духе // W: ....., а также строки на которых заведомо не должно быть сообщений // OK) и в самом файле есть разметка содержащая выхлоп тестовой программы.
.
.
24.09.2012 09:09
Здравствуйте, hardcase, Вы писали:

.>>И никаких магических регекспов.


H>Все не так просто, "магические регекспы" помимо шаблона сообщения несут в себе еще и номер строки.

Номер строки можно тоже проверять, у меня там проверяется.

H>Приведенный пример — это негативный тест, т.е. файл, компиляция которого завершается с ошибкой. Есть еще и позитивные тесты — они компилируются без ошибки (но возможно с предупреждениями, которые нужно аннотировать в духе // W: ....., а также строки на которых заведомо не должно быть сообщений // OK) и в самом файле есть разметка содержащая выхлоп тестовой программы.

А, понял. В смысле сам тестовый файл используется как источник того, что надо проверить? Выцепляются комменты вида "// [WE]:" из текста специальной тулзой?
Да, тоже интересный подход, но наверное слишком специфичный для компиляторов.
AndrewVK
AndrewVK
26.09.2012 01:48
Здравствуйте, ., Вы писали:

.>Мне кажется, что для тестов часто нужен не только код, но и параметры.


Для тестов ничего парсить не нужно, достаточно точного соответствия полного сообщения.
... << RSDN@Home 1.2.0 alpha 5 rev. 65 on Windows 8 6.2.9200.0>>
Sinix
Sinix
24.09.2012 01:41
Здравствуйте, AndrewVK, Вы писали:

AVK>Задача: есть некий обработчик, для простоты пусть это будет компилятор. В процессе своей работы он может выдавать некоторое количество ошибок. Каждая ошибка характеризуется местом возникновения (файл, строка, колонка), уникальным символьным кодом и текстом с опциональными параметрами на нескольких языках. При этом текст должен выдаваться на языке, который выставлен у потока, выводящего ошибки, а не у потока компиляции (они могут не совпадать).

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

В теории будет достаточно отдельного resX-файла с описанием текста ошибок + замены генератора на кастомный на t4 + базового типа, чтобы генерить поменьше кода. Разумеется, придётся придерживаться определённой схемы именования ресурсов (..._Message, ..._Param0, ..._Param1 etc), чтобы генератор понимал что есть что.

На практике с прямым использованием .tt-файлов вечно возникают грабли, особенно если распространять tt-шаблоны как часть проекта. Навскидку, в 2008й приходилось править ключи реестра для использования относительных путей к шаблонам. На мой взгляд, проще использовать свой custom tool, и из него вызывать t4-процессор.

Мне попадались примеры с заменой генератора resx (насколько оно рабочее на практике — хз)
http://www.codeproject.com/Articles/31907/Customize-the-Code-Generated-by-the-Resources-Desi

http://www.codeproject.com/Articles/269362/Using-T4-Templates-to-generate-custom-strongly-typ
http://blog.baltrinic.com/software-development/dotnet/t4-template-replace-resxfilecodegenerator

+ можно использовать T4 Toolbox
http://www.olegsych.com/2009/11/t4-toolbox-automatic-template-transformation/

Если не подходит — всегда можно будет извратиться с IVsSingleFileGenerator
Nuseraro
Nuseraro
26.09.2012 11:27
Здравствуйте, AndrewVK, Вы писали:

AVK>Задача: есть некий обработчик, для простоты пусть это будет компилятор. В процессе своей работы он может выдавать некоторое количество ошибок. Каждая ошибка характеризуется местом возникновения (файл, строка, колонка), уникальным символьным кодом и текстом с опциональными параметрами на нескольких языках. При этом текст должен выдаваться на языке, который выставлен у потока, выводящего ошибки, а не у потока компиляции (они могут не совпадать).

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

Если я правильно понял задачу, то:

Если принципиальное абстрактное решение, то:
Есть "фреймворк ошибок": базовые классы ошибок, хэлперы всякие.
Есть хранилище ресурсов — я так понял вы непременно хотите resx.
Есть хранилище дополнительной информации об ошибках: соответствие кодов и сообщений/северити/кол-ва доп параметров
Есть генерилка, которая на основе фреймворка и 2ух хранилищ делает результирующие ошибки.

Как я понимаю, это фактически просто переформулировка требований и тут врядли какие-то вопросы.

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

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

Что именно вам нужно во "фреймворке" не очень понятно, пока приходит на ум только простенький ErrorBase. Возможно понадобятся хэлперы для вычисления языка, строчки и т.д.