Как автоматически генерировать код, который должен быть написан для каждого метода?

Я работаю над проектом, и каждый отдельный метод, который я пишу, начинается с:

public blah blah() // my method signature
{
    Tracing.StartOfMethod("Repositroy");

    // I'll declare variables as needed

    try
    {
        // the content here differs for every method
    }
    catch (Exception ex)
    {
        ErrorSignal.FromCurrentContext().Raise(ex);
        // sometimes I'll add something here
    }
    finally
    {
        // sometimes something here
        Tracing.EndOfMethod();
    }

    // return result
}

Каждый метод заканчивается по-разному в зависимости от того, что он делает, но я всегда начинаю с одной и той же структуры. Может кто-нибудь сказать мне, если есть способ автоматизировать написание этого кода, чтобы он не был таким повторяющимся? Может быть "Вставить фрагмент"? Если так, как я могу определить фрагменты? Благодарю.

8 ответов

Решение

Я бы согласился с @Kent и другими, что AoP выглядит здесь хорошо, а также с @sleske, что необходимость делать это для каждого метода предполагает, что у вас могут возникнуть проблемы с дизайном.

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

public T DoMethod<T>( Func<T> mainCode, Func<Exception, T> exceptionHandlerCode)
{
    Tracing.StartOfMethod("Repository");

    try
    {
        return mainCode.Invoke();
    }
    catch (Exception ex)
    {
         ErrorSignal.FromCurrentContext().Raise(ex);
         return exceptionHandlerCode.Invoke(ex);
    }
    finally
    {
        // sometimes something here
        Tracing.EndOfMethod();
    }
}

Который можно назвать, например, лямбда-выражением:

DoMethod(() => { return 5; }, ex => { return 0; })

Вы можете посмотреть на AoP-решение, такое как PostSharp.

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

Вы бы сделали что-то вроде этого, что эффективно сгенерирует код, который у вас есть выше:

public class TraceAttribute : OnMethodBoundaryAspect
{
    public override void OnEntry(MethodExecutionEventArgs eventArgs)
    { 
        Tracing.StartOfMethod("Repository");
    }

    public override void OnExit(MethodExecutionEventArgs eventArgs)
    { 
        Tracing.EndOfMethod();
    }
}

Как указывает Кент Бугаарт, это именно тот сценарий, для которого было изобретено Аспектно-ориентированное программирование. Таким образом, вы можете использовать это или просто использовать некоторый механизм сниппета для вставки кода в качестве шаблона в вашу IDE.

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

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

В этом случае сам твой шаблон уже кажется мне очень проблематичным:

  • Отслеживание каждого вызова метода кажется излишним. Если вам это действительно нужно, просто подключите отладчик, вот для чего они. Если вы хотите отследить материал в производственном процессе, гораздо полезнее иметь значимые сообщения журнала ("Запуск обработки frob...", "обработанные объекты x glurg"). Это также поможет, например, sysadmins, тогда как ваши сообщения полезны, только если вы знаете исходный код.
  • Поймать все исключения в каждом методе, на мой взгляд, совершенно непростительно. Исключением является то, что вы можете позволить им распространяться. Ловите только локально исключения, которые вы можете обрабатывать индивидуально; остальное должно идти к универсальному обработчику стека вызовов.

Возможно, вы могли бы объяснить свою мотивацию для этого кода?

Доступны различные редакторы фрагментов кода, ни один из которых я никогда не использовал, но есть один на Codeplex, о котором я уже упоминал ранее: редактор фрагментов.

Вот фрагмент кода того, что вы могли бы хотеть. Просто создайте файл.snippet и поместите его в каталог сниппетов.

<?xml version="1.0" encoding="utf-8"?> <CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet"> <CodeSnippet Format="1.0.0">
    <Header>
      <SnippetTypes>
        <SnippetType>Expansion</SnippetType>
      </SnippetTypes>
      <Title>commonmethodsnippet</Title>
      <Shortcut>commonmethodsnippet</Shortcut>
      <Description>Common method snippet.</Description>
      <Author>Me</Author>
    </Header>
    <Snippet>
      <Declarations>
        <Literal Editable="true">
          <ID>returnValue</ID>
          <ToolTip>Return Value</ToolTip>
          <Default>returnValue</Default>
          <Function>
          </Function>
        </Literal>
        <Literal Editable="true">
          <ID>methodName</ID>
          <ToolTip>Method Name</ToolTip>
          <Default>methodName</Default>
          <Function>
          </Function>
        </Literal>
        <Literal Editable="true">
          <ID>methodDescription</ID>
          <ToolTip>Method Description</ToolTip>
          <Default>methodDescription</Default>
          <Function>
          </Function>
        </Literal>
        <Literal Editable="true">
          <ID>variableDeclarations</ID>
          <ToolTip>Variable Declarations</ToolTip>
          <Default>variableDeclarations</Default>
          <Function>
          </Function>
        </Literal>
      </Declarations>
      <Code Language="csharp"><![CDATA[public $returnValue$ $methodName$() // $methodDescription$  { 
    Tracing.StartOfMethod("Repository"); 

    // Variable Declarations
    $variableDeclarations$

    try 
    { 
        // the content here differs for every method 
    } 
    catch (Exception ex) 
    { 
        ErrorSignal.FromCurrentContext().Raise(ex);

        // sometimes I'll add something here 
    } 
    finally 
    { 
        // sometimes something here 
        Tracing.EndOfMethod(); 
    } 

    // return result  }]]></Code>
    </Snippet>   </CodeSnippet> </CodeSnippets>

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

Основываясь на статье TraceListeners and Reflection, выполненной Джерри Деннани, я расширил каркас логирования Object Guy, чтобы получить след от моего кода. Это может быть дополнительно обработано средствами ведения журналов, но я не беспокоюсь - иногда очень конструктивно просто сканировать результаты.

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

<CodeSnippet Format="1.0.0">
<Header>
  <Title>Canonic</Title>
  <SnippetTypes>
    <SnippetType>Expansion</SnippetType>
  </SnippetTypes>
</Header>
<Snippet>
  <Declarations>
    <Literal>
      <ID>ClassName</ID>
      <ToolTip>Replace with the name of the class.</ToolTip>
      <Default>ClassName</Default>
    </Literal>
    <Literal>
      <ID>MethodName</ID>
      <ToolTip>Replace with the name of the method.</ToolTip>
      <Default>MethodName</Default>
    </Literal>
    <Literal>
      <ID>FirstArgName</ID>
      <ToolTip>Replace with the name of the first argument.</ToolTip>
      <Default>FirstArgName</Default>
    </Literal>
    <Literal>
      <ID>SecondArgName</ID>
      <ToolTip>Replace with the name of the second argument.</ToolTip>
      <Default>SecondArgName</Default>
    </Literal>
    <Literal>
      <ID>ResultName</ID>
      <ToolTip>Replace with the name of the result.</ToolTip>
      <Default>ResultName</Default>
    </Literal>
  </Declarations>
  <Code Language="CSharp">
    <![CDATA[            Logger.LogMethod("$FirstArgName$", $FirstArgName$,"$SecondArgName$", $SecondArgName$);
        try
        {
            Validator.Verify($FirstArgName$, $SecondArgName$);
            //VerifyFields();

            Logger.LogReturn($ResultName$);
            return $ResultName$;

        }
        #region Exception
        catch (Exception exp)
        {
            Logger.LogException("$ClassName$.$MethodName$", exp);
            throw;
        }
        #endregion Exception
   ]]>
  </Code>
</Snippet>

Вы можете посмотреть эту ссылку на MSDN, чтобы узнать, как создавать / использовать фрагменты. Я согласен с @Kent, хотя решение AOP будет лучшим.

Другие вопросы по тегам