úterý 6. září 2011

Jak psát lepší kód s využitím StyleCopu

StyleCop - stylový policajt

StyleCop patří do rodiny nástrojů pro provádění statické analýzy kódu. StyleCop prověřuje kód a to bez nutnosti tento kód spouštět - proto statická analýza. StyleCop umí prověřovat (validovat) pravidla z několika oblastí - dokumentační pravidla, pravidla rozvržení, pravidla pro udržovatelnost, pravidla pojmenování, pravidla pro řazení, pravidla pro čitelnost, pravidla pro řádkování a mezery. Můžete si vytvořit i vlastní. Která pravidla nakonec budete chtít vynucovat záleží na Vámi zvoleném způsobu integrace tohoto nástroje do vývojového procesu.

StyleCop je zdarma ke stažení na CodePlexu, viz. stylecop.codeplex.com. Jako platforma použití se předpokládá Visual Studio 2008 nebo 2010 v edici minimálně Professional. V dokumentaci lze dohledat návod, jak docílit použití spolu s Visual Studiem 2005 nebo s Express edicí. StyleCop je v edicích Premium a Ultimate nativní součástí distribuce. Podobné integrace můžete dosáhnout dle návodu popsaného níže.

Ukázka použití StyleCopu

Nastal čas, aby nám StyleCop ukázal co umí. Fiktivní třída AntiStyleCopClass se tváří poměrně nenápadně. Je syntakticky správně a působí poměrně logicky strukturovaná. Posuďte sami:

using System;
using System.Text;
using System.Collections.Generic;

namespace StyleCopTest
{
    /// <summary>
    /// Třída, která se StyleCopu nebude líbit 
    ///</summary>
    class AntiStyleCopClass                                    /* line 10*/
    {
        private const string ODDELOVAC_TEXTU = " ";
        protected string oddelovacTextu = ODDELOVAC_TEXTU;

        /// <summary>
        /// Bezparametrický konstruktor.
        /// </summary>
        public AntiStyleCopClass() { }

        /// <summary>                                          /* line 20*/
        /// Konstruktor s definicí oddělovače textu.
        /// </summary>
        public AntiStyleCopClass(string oddelovacTextu)
        {
            this.oddelovacTextu=oddelovacTextu;
        }


        private void SoukromaMetoda(string argument1, string argument2,string argument3) {
                                                               /* line 30*/
            if ((argument1 == "") || argument1 == null) { return; }
            if (argument2 == String.Empty || argument2 == null )
            // Rychle opustit metodu
            {
                return;
            };
            if (String.IsNullOrEmpty(argument3)) { return; } else { Console.WriteLine(argument1 + argument2 + argument3); }
        }
        /// <summary>
        /// Veřejná metoda třídy.                              /* line 40*/
        /// </summary>
        /// <param name="argument1">Argument1.</param>
        /// <param name="parametr2">Argument2</param>
        public void VerejnaMetoda(string argument1, string argument2)
        {
            if (argument1 == null || argument2 == null) throw new ArgumentNullException("Takové argumenty Ti nesežeru.");

            // Vyvolání soukromé metody

            SoukromaMetoda(argument1,                          /* line 50*/
                oddelovacTextu, argument2
                );   

            //Console.WriteLine("Zpracování ok.");
        }

        /// <summary>
        /// Oddělovač textu.
        /// </summary>
                                                               /* line 60*/
        public string OddelovacTextu
        {
            get {return oddelovacTextu;}
            set
            {
                oddelovacTextu = value;
            }
        }

    }                                                          /* line 70*/

    /// <summary>
    /// Takový malý appendix.
    /// </summary>
    public class JednaMalaTrida
    {
    }
}                                                            

Oko zkušeného vývojáře však zbystří a začne klopýtat po méně či více závažných proviněních proti formátovacím a kódovacím standardům jazyka C# a dobrým programátorským mravům. Necháme-li úřadovat StyleCop, dostáváme v tomto případě 46 porušení validačních pravidel. Všimněte si, každé pravidlo má jednoznačný identifikační kód a vysvětlující text porušení.

Line 2: SA1210: Using directives must be sorted alphabetically by the namespaces.
Line 9: SA1004: The documentation header line must start with a single space.
Line 10: SA1629: The documentation text within the summary tag must end with a period.
Line 10: SA1400: The class must have an access modifier.
Line 10: SA1202: All internal classes must be placed after all public classes.
Line 12: SA1310: Field names must not contain underscores.
Line 12: SA1202: All private fields must be placed after all protected fields.
Line 13: SA1600: The field must have a documentation header.
Line 13: SA1401: Fields must be declared with private access. Use properties to expose fields.
Line 18: SA1502: The constructor must not be placed on a single line. The opening and closing curly brackets must each be placed on their own line.
Line 23: SA1611: The documentation header must contain param tags matching the element's parameter list.
Line 25: SA1003: The spacing around the symbol '=' is invalid.
Line 28: SA1507: The code must not contain multiple blank lines in a row.
Line 29: SA1500: If a statement spans multiple lines, the opening curly bracket must be placed on its own line.
Line 29: SA1505: An opening curly bracket must not be followed by a blank line.
Line 29: SA1001: Invalid spacing around the comma.
Line 31: SA1501: A statement containing curly brackets must not be placed on a single line. The opening and closing curly brackets must each be placed on their own line.
Line 31: SA1122: Use string.Empty rather than "".
Line 32: SA1009: Invalid spacing around the closing parenthesis.
Line 33: SA1515: A single-line comment must be preceded by a blank line or another single-line comment, or must be the first item in its scope. To ignore this error when commenting out a line of code, begin the comment with '////' rather than '//'.
Line 33: SA1108: A comment may not be placed within the bracketed statement.
Line 36: SA1106: The code contains an extra semicolon.
Line 37: SA1501: A statement containing curly brackets must not be placed on a single line. The opening and closing curly brackets must each be placed on their own line.
Line 37: SA1107: A line may only contain a single statement.
Line 39: SA1514: A documentation header must be preceded by a blank line or must be the first item in its scope.
Line 44: SA1202: All private methods must be placed after all public methods.
Line 44: SA1612: The param tags in the documentation header must match the element's parameter list.
Line 44: SA1629: The documentation text within the param tag must end with a period.
Line 44: SA1632: The documentation text within the param tag must be at least 10 characters in length. Documentation failing to meet this guideline most likely does not follow a proper grammatical structure required for documentation text.
Line 44: SA1516: Adjacent elements must be separated by a blank line.
Line 46: SA1503: The body of the if statement must be wrapped in opening and closing curly brackets.
Line 48: SA1512: A single-line comment must not be followed by a blank line. To ignore this error when commenting out a line of code, begin the comment with '////' rather than '//'.
Line 50: SA1117: All method parameters must be placed on the same line, or each parameter must be placed on a separate line.
Line 50: SA1116: If the method parameters are on separate lines, the first parameter must begin on the line beneath the name of the method.
Line 51: SA1115: The parameter must begin on the line after the previous parameter.
Line 52: SA1111: The closing parenthesis or bracket must be placed on the same line as the last parameter in the parameter list.
Line 52: SA1009: Invalid spacing around the closing parenthesis.
Line 54: SA1005: The comment must start with a single space. To ignore this error when commenting out a line of code, begin the comment with '////' rather than '//'.
Line 57: SA1506: A documentation header line must not be followed by a blank line.
Line 61: SA1201: All methods must be placed after all properties.
Line 63: SA1504: An accessor can only be placed on a single line if all of the accessors in the property are placed on a single line.
Line 63: SA1012: Invalid spacing around the opening curly bracket.
Line 63: SA1002: Invalid spacing around the semicolon.
Line 63: SA1013: Invalid spacing around the closing curly bracket.
Line 70: SA1508: A closing curly bracket must not be preceded by a blank line.
Line 75: SA1402: A C# document may only contain a single class at the root level unless all of the classes are partial and are of the same type.

Tento výsledek by nás neměl znechutit, ale spíše motivovat. Chceme přeci vytvářet lepší kód?! Soustředíme se, postupně si pozorně pročítáme každou chybu a sjednáváme nápravu. Ve výsledku dostáváme třídu LovelyStyleCopClass, na které nás StyleCop již nenachytá:

using System;
using System.Collections.Generic;
using System.Text;

namespace StyleCopTest
{
    /// <summary>
    /// Třída, která se StyleCopu musí líbit.
    /// </summary>
    public class LovelyStyleCopClass
    {
        private const string OddelovacTextuDefaultni = " ";
        private string oddelovacTextu = OddelovacTextuDefaultni;

        /// <summary>
        /// Bezparametrický konstruktor.
        /// </summary>
        public LovelyStyleCopClass() 
        {
        }

        /// <summary>
        /// Konstruktor s definicí oddělovače textu.
        /// </summary>
        /// <param name="oddelovacTextu">Oddělovač textu.</param>
        public LovelyStyleCopClass(string oddelovacTextu)
        {
            this.oddelovacTextu = oddelovacTextu;
        }

        /// <summary>
        /// Oddělovač textu.
        /// </summary>
        public string OddelovacTextu
        {
            get
            {
                return oddelovacTextu;
            }

            set
            {
                oddelovacTextu = value;
            }
        }

        /// <summary>
        /// Veřejná metoda třídy.
        /// </summary>
        /// <param name="argument1">Argument1.</param>
        /// <param name="argument2">Argument2.</param>
        public void VerejnaMetoda(string argument1, string argument2)
        {
            // Vyvolání soukromé metody
            SoukromaMetoda(argument1, oddelovacTextu, argument2);   
        }

        private void SoukromaMetoda(string argument1, string argument2, string argument3)
        {
            if (argument1 == String.Empty || argument1 == null)
            {
                return;
            }

            if (argument2 == String.Empty || argument2 == null)
            {
                // Rychle opustit metodu
                return;
            }

            if (String.IsNullOrEmpty(argument3))
            {
                return;
            }
            else
            {
                Console.WriteLine(argument1 + argument2 + argument3);
            }
        }
    }
}

Způsoby integrace StyleCopu do vývojového procesu

Způsob použití je odvislý od fáze, ve které chcete statickou analýzu kódu provádět:

  • Ruční spouštění. V tomto případě si musíte StyleCop nainstalovat lokálně. Po instalaci se StyleCop zaintegruje do vývojového prostředí Visual Studia a v Solution Exploreru Vám přibude nová kontextová volba StyleCop. Validaci můžete spustit nad jedním nebo více cs soubory, nad projektem nebo libovolným podstromem řešení. Problémový výsledek validace se projeví přidáním varování (warning) v okně s chybami kompilace (Error List). Kontextová nabídka na varováních, která přidal StyleCop obsahuje volbu Show Error Help. Ta zobrazí pro každé pravidlo pěkně vypracovanou dokumentaci, ve které se mimojiné dozvíte, proč je porušení pravidla problémové a jak jeho porušení vyřešit. Tento přístup je vhodný pro seznámení se se StyleCopem a v situaci, kdy integrujete StyleCop do vývoje projektu, který již běží.
  • Spouštění v rámci kompilace projektu. Tento přístup je logický, neboť kompilace projektu je okamžik, kdy Visual Studio (konkrétně MSBuild) vyhodnocuje nejen syntaktickou správnost, ale také další pravidla. Přidání StyleCopu znamená rozšířit .csproj konfigurační soubor o vyvolání targetu, který je součástí distribuce StyleCopu:
    ...
    <!-- Kompilace projektu - standardní řádek v každém kompilovatelném projektu -->
    <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> 
    <!-- Integrace StyleCopu - nově přidaný řádek -->  
    <Import Project="c:\Program Files\MSBuild\Microsoft\StyleCop\v4.4\Microsoft.StyleCop.Targets" /> 
    ...
    
    Modifikací tohoto přístupu je varianta, kdy umístíte všechny potřebné knihovny StyleCopu včetně .Targets souboru do repository. Pak budete v .csproj souborech odkazovat relativní cestou. Tímto způsobem zajistíte pro ostatní členy týmu bezinstalační integraci StyleCopu. Počítejte s tím, že přidání validace do kompilace projektu Vám nepatrně prodlouží dobu kompilace.
  • Spouštění v rámci denního buildu. Můžete nechat StyleCop prověřovat Váš kód během denního serverového buildu, který je určen pro kontinuální zajištění kvality kódu. V rámci tohoto buildu můžete provádět různé techniky statického i dynamického validování. V tomto případě je nutné přehledným způsobem zobrazit případná porušení například na vývojovém týmovém portálu. Tento způsob spouštění poskytuje důležité informace pro vedoucího vývojáře a pro tým zajišťující kvalitu vývoje.

Výše uvedené přístupy lze samozřejmě kombinovat a dle mého názoru je to i užitečné. Pro vývojáře je nejlepší, aby byl na porušení upozorněn co nejdříve. Tedy nejpozději při kompilaci projektu.

Bez pomocných nástrojů to půjde tězce

Pokud si stanovíte přísná pravidla na vytvářený kód, počítejte s tím, že režie na vytváření kódu se zvýší. Než si na nová pravidla zvyknete, bude to chvíli trvat. Brzy se však naučíte dodržovat konvence s takovou samozřejmostí, že StyleCop ani nebude mít mnoho práce. Navýšení pracnosti je však pouze relativní. Kód je součástí projektu dlouhé roky a tak je nutné odlišovat primární náklady související s prvotním vytvářením kódu a sekundární náklady spojené s jeho údržbou a pozdější modifikací. A právě v redukci sekundárních nákladů je StyleCop silný. Každá pozdější práce se správně napsaným kódem je efektivnější a začíná se Vám úročit dodržování pravidel StyleCopu.

Dodržování některých pravidel je bez pomocných nástrojů hodně pracné. Např. pravidlo SA1202: ElementsMustBeOrderedByAccess požaduje, aby pořadí členů třídy bylo setříděno podle přístupnosti (public, internal, protected internal, protected, private). Ruční hlídání tohoto pravidla by mohlo být pracné. Existuje však nástroj Regionerate, který Vám zautomatizuje správné setřídění členů.

Při dodržování mnoha dokumentačních pravidel Vám ulehčí práci doplněk GhostDoc.

Určitě se vyplatí používat další doplňky typu CodeRush. Např. jedno z pravidel SA1210: UsingDirectivesMustBeOrderedAlphabeticallyByNamespace požaduje, aby using direktivy byly setříděny podle abecedy. Ruční editace nepředstavitelná, s CodeRush je to hračka. Stačí podržet kurzor myši nad problémovou oblastí a máte navrženu úpravu. Pro CodeRush existuje doplněk, který nastaví chování CodeRush podle pravidel vynucovaných StyleCopem.

Pro editaci .csproj souborů používám doplněk PowerCommands. Ten umí také třídit usingy, viz. předchozí odstavec.

Která pravidla zvolit?

Parametrizaci pravidel má StyleCop uloženou v souboru Settings.StyleCop. Pro práci s konfiguračním souborem je dodáván vizuální editor. Vyberte si pravidla vhodná pro Váš projekt a uložte konfigurační soubor do rootu Vašeho adresářového stromu se zdrojovými kódy. StyleCop při spuštění prochází nadadresáře a hledá konfigurační soubor, kterým se následně řídí. Můžete některé podstromy s kódem nakonfigurovat přísněji, jiné benevolentněji.

Níže uvádím výpis pravidel, které používáme v našich projektech. Můžete se nechat inspirovat.

Dokumentační pravidla (Documentation Rules)
SA1600: ElementsMustBeDocumented
SA1601: PartialElementsMustBeDocumented
SA1602: EnumerationItemsMustBeDocumented
SA1603: DocumentationMustContainValidXml
SA1604: ElementDocumentationMustHaveSummary
SA1605: PartialElementDocumentationMustHaveSummary
SA1606: ElementDocumentationMustHaveSummaryText
SA1607: PartialElementDocumentationMustHaveSummaryText
SA1608: ElementDocumentationMustNotHaveDefaultSummary
SA1610: PropertyDocumentationMustHaveValueText
SA1611: ElementParametersMustBeDocumented
SA1612: ElementParameterDocumentationMustMatchElementParameters
SA1613: ElementParameterDocumentationMustDeclareParameterName
SA1614: ElementParameterDocumentationMustHaveText
SA1615: ElementReturnValueMustBeDocumented
SA1616: ElementReturnValueDocumentationMustHaveValue
SA1617: VoidReturnValueMustNotBeDocumented
SA1618: GenericTypeParametersMustBeDocumented
SA1619: GenericTypeParametersMustBeDocumentedPartialClass
SA1620: GenericTypeParameterDocumentationMustMatchTypeParameters
SA1621: GenericTypeParameterDocumentationMustDeclareParameterName
SA1622: GenericTypeParameterDocumentationMustHaveText
SA1624: PropertySummaryDocumentationMustOmitSetAccessorWithRestricedAccess
SA1625: ElementDocumentationMustNotBeCopiedAndPasted
SA1626: SingleLineCommentsMustNotUseDocumentationStyleSlashes
SA1627: DocumentationTextMustNotBeEmpty
SA1628: DocumentationTextMustBeginWithACapitalLetter
SA1629: DocumentationTextMustEndWithAPeriod
SA1631: DocumentationTextMustMeetCharacterPercentage
SA1632: DocumentationTextMustMeetMinimumCharacterLength
SA1636: FileHeaderMustContainFileName
SA1643: DestructorSummaryDocumentationMustBeginWithStandardText
SA1644: DocumentationHeadersMustNotContainBlankLines
SA1645: IncludedDocumentationFileDoesNotExist
SA1646: IncludedDocumentationXPathDoesNotExist
SA1647: IncludeNodeDoesNotContainValidFileAndPath

Pravidla rozvržení (Layout Rules)
SA1500: CurlyBracketsForMultiLineStatementsMustNotShareLine
SA1501: StatementMustNotBeOnSingleLine
SA1502: ElementMustNotBeOnSingleLine
SA1503: CurlyBracketsMustNotBeOmitted
SA1504: AllAccessorMustBeMultiLineOrSingleLine
SA1505: OpeningCurlyBracketsMustNotBeFollowedByBlankLine
SA1506: ElementDocumentationHeadersMustNotBeFollowedByBlankLine
SA1507: CodeMustNotContainMultipleBlankLinesInARow
SA1508: ClosingCurlyBracketsMustNotBePrecededByBlankLine
SA1509: OpeningCurlyBracketsMustNotBePrecedededByBlankLine
SA1510: ChainedStatementBlocksMustNotBePrecededByBlankLine
SA1511: WhileDoFooterMustNotBePrecededByBlankLine
SA1512: SingleLineCommentsMustNotBeFollowedByBlankLine
SA1513: ClosingCurlyBracketMustBeFollowedByBlankLine
SA1514: ElementDocumentationHeaderMustBePrecededByBlankLine
SA1515: SingleLineCommentMustBePrecededByBlankLine
SA1516: ElementsMustBeSeparatedByBlankLine

Pravidla pro udržovatelnost (Maintainability Rules)
SA1119: StatementMustNotUseUnnecessaryParenthesis
SA1400: AccessModifierMustBeDeclared
SA1401: FieldsMustBePrivate
SA1402: FileMayOnlyContainASingleClass
SA1403: FileMayOnlyContainASingleNamespace
SA1404: CodeAnalysisSuppressionMustHaveJustification
SA1405: DebugAssertMustProvideMessageText
SA1406: DebugFailMustProvideMessageText
SA1407: ArithmeticExpressionsMustDeclarePrecedence
SA1408: ConditionalExpressionsMustDeclarePrecendence
SA1409: RemoveUnnecessaryCode
SA1410: RemoveDelegateParenthesisWhenPossible

Pravidla názvosloví (Naming Rules)
SA1300: ElementMustBeginWithUpperCaseLetter
SA1301: ElementMustBeginWithLowerCaseLetter
SA1302: InterfaceNamesMustBeginWithI
SA1303: ConstFieldNamesMustBeginWithUpperCaseLetter
SA1304: NonPrivateReadonlyFieldsMustBeginWithUpperCaseLetter
SA1305: FieldNamesMustNotUseHungarianNotation
SA1306: FieldNamesMustBeginWithLowerCaseLetter
SA1307: AccessibleFieldsMustBeginWithUpperCaseLetter
SA1308: VariableNamesMustNotBePrefixed
SA1309: FieldNamesMustNotBeginWithUnderscore
SA1310: FieldNamesMustNotContainUnderscore

Pravidla pořadí (Ordering Rules)
SA1201: ElementsMustAppearInTheCorrectOrder
SA1202: ElementsMustBeOrderedByAccess
SA1203: ConstantsMustAppearBeforeFields
SA1204: StaticElementsMustAppearBeforeInstanceElements
SA1206: DeclarationKeywordsMustFollowOrder
SA1207: ProtectedMustComeBeforeInternal
SA1208: SystemUsingDirectivesMustBePlacedBeforeOtherUsingDirectives
SA1209: UsingAliasDirectivesMustBePlacedAfterOtherUsingDirectives
SA1210: UsingDirectivesMustBeOrderedAlphabeticallyByNamespace
SA1211: UsingAliasDirectivesMustBeOrderedAlphabeticallyByAliasName
SA1212: PropertyAccessorsMustFollowOrder
SA1213: EventAccessorsMustFollowOrder

Pravidla pro čitelnost (Readability Rules)
SA1100: DoNotPrefixCallsWithBaseUnlessLocalImplementationExists
SA1102: QueryClauseMustFollowPreviousClause
SA1103: QueryClausesMustBeOnSeparateLinesOrAllOnOneLine
SA1104: QueryClauseMustBeginOnNewLineWhenPreviousClauseSpansMultipleLines
SA1105: QueryClausesSpanningMultipleLinesMustBeginOnOwnLine
SA1106: CodeMustNotContainEmptyStatements
SA1107: CodeMustNotContainMultipleStatementsOnOneLine
SA1108: BlockStatementsMustNotContainEmbeddedComments
SA1109: BlockStatementsMustNotContainEmbeddedRegions
SA1110: OpeningParenthesisMustBeOnDeclarationLine
SA1111: ClosingParenthesisMustBeOnLineOfOpeningParenthesis
SA1112: ClosingParenthesisMustBeOnLineOfOpeningParenthesis
SA1113: CommaMustBeOnSameLineAsPreviousParameter
SA1114: ParameterListMustFollowDeclaration
SA1115: ParameterMustFollowComma
SA1116: SplitParametersMustStartOnLineAfterDeclaration
SA1117: ParametersMustBeOnSameLineOrSeparateLines
SA1118: ParameterMustNotSpanMultipleLines
SA1120: CommentsMustContainText
SA1122: UseStringEmptyForEmptyStrings
SA1123: DoNotPlaceRegionsWithinElements

Pravidla pro řádkování a mezery (Spacing Rules)
SA1000: KeywordsMustBeSpacedCorrectly
SA1001: CommasMustBeSpacedCorrectly
SA1002: SemicolonsMustBeSpacedCorrectly
SA1003: SymbolsMustBeSpacedCorrectly
SA1004: DocumentationLinesMustBeginWithSingleSpace
SA1005: SingleLineCommentsMustBeginWithSingeSpace
SA1006: PreprocessorKeywordsMustNotBePrecededBySpace
SA1007: OperatorKeywordMustBeFollowedBySpace
SA1008: OpeningParenthesisMustBeSpacedCorrectly
SA1009: ClosingParenthesisMustBeSpacedCorrectly
SA1010: OpeningSquareBracketsMustBeSpacedCorrectly
SA1011: ClosingSquareBracketsMustBeSpacedCorrectly
SA1012: OpeningCurlyBracketsMustBeSpacedCorrectly
SA1013: ClosingCurlyBracketsMustBeSpacedCorrectly
SA1014: OpeningGenericBracketsMustBeSpacedCorrectly
SA1015: ClosingGenericBracketsMustBeSpacedCorrectly
SA1016: OpeningAttributeBracketsMustBeSpacedCorrectly
SA1017: ClosingAttributeBracketsMustBeSpacedCorrectly
SA1018: NullableTypeSymbolsMustNotBePrecededBySpace
SA1019: MemberAccessSymbolsMustBeSpacedCorrectly
SA1020: IncrementDecrementSymbolsMustBeSpacedCorrectly
SA1021: NegativeSignsMustBeSpacedCorrectly
SA1022: PositiveSignsMustBeSpacedCorrectly
SA1023: DereferenceAndAccessOfSymbolsMustBeSpacedCorrectly
SA1024: ColonsMustBeSpacedCorrectly
SA1025: CodeMustNotContainMultipleWhitespaceInARow
SA1026: CodeMustNotContainSpaceAfterNewKeywordInImplicitlyTypedArrayAllocation
SA1027: TabsMustNotBeUsed

StyleCop nabízí podporu pro přidávání vlastních validačních pravidel. Vytvořit vlastní pravidlo není příliš složité. Příklad by se možná mohl objevit na tomto blogu jako článek v budoucnu. Pokud bude poptávka :)

Závěrem

Pokud Vás článek zaujal, můžete připsat komentář. Můžete se také zeptat na zkušenosti z praxe i na další aspekty použití StyleCopu.

Používáte jinou statickou analýzu? Jakou? Jak jste s ní spokojeni?