Привет хабр! Основная речь пойдет про разработку на .Net, то есть с использованием Microsoft Visual Studio, ReSharper, Nuget и пр.
Я думаю, многие из вас разрабатывали большие решения (в msdn — solution), со множеством подпроектов. И в этом случае нередко становилась проблема синхронизации Nuget пакетов, настроек сборки и т.д. Причем, ReSharper здесь поможет слабо, разве что он тоже начнет путаться во множестве используемых библиотек.
Чтобы проверять исходный код, было сделано Open Source решение — SolutionCop, которое бесплатно для использования.
Для начала приведу парочку примеров, когда не помешали бы проверки наших решений.

Пример 1: разные версии Nuget библиотек.


Например, есть три проекта: exe, dll1 и dll2. exe ссылается на обе библиотеки, каждая из них ссылается, например, на RX. Но dll1 использует RX 2.2.0, а dll2 — RX 2.2.5. На деле, далеко не сразу можно получить ошибку, так как сигнатуры функций более-менее совпадают, более того, MsBuild чаще всего собирает проекты в одном и то же порядке. Однако подобная конфигурация может привести к проблемам, которые появятся после deployment'а, когда все модульные тесты пройдут (т.к. они ссылаются только на свою библиотеку), и когда будет готовиться результирующий набор файлов.

Пример 2: проект ссылается на библиотеку напрямую, а не через Nuget.


Опять возьмем три наших проекта: exe, dll1 и dll2. Допустим, мы также используем еще Jetbrains.Annotations, чтобы размечать код NotNull/CanBeNull аттрибутами и получать симпатичный статический анализ. Но вот незадача: для dll1 мы честно скачали пакет версии 9.2.0, а в dll2 мы просто попросили ReSharper добавить ссылку, что он и сделал. В итоге, в packages.config файле dll2 нет пакета с аттрибутами, а значит, если проект будет собираться в порядке dll2 --> dll1 --> exe, то мы получим ошибку, ведь Nuget пакет скачается только при сборе dll1!

Можно привести еще ряд примеров, когда разные настройки в проектах могут привести к веселым проблемам. Например, проекты для .Net 4.0 и .Net 4.5 могут ссылаться на одинаковые пакеты, но на разные библиотеки в них (см. Nuget help), а значит мы опять будем получать спецэффекты при сборке проектов. Однако лучше перейдем к SolutionCop.

Установка


SolutionCop доступен на Nuget, устанавливается он на уровень всего решения. После установки пакета необходимо создать xml файл с правилами, настроить их и встроить проверяльщика в процедуру сборки на CI.
Пошагово:
  1. Создаем xml файл с правилами (будем проверять сам SolutionCop):

    SolutionCop.exe -s "C:\git\SolutionCop\src\SolutionCop.sln"

    После этой команды возле sln файла появится файл SolutionCop.xml. В нем перечислены все правила, но все они выключены. Рассмотрим их позже.
  2. Настраиваем CI сервер так, чтобы SolutionCop запускался при каждом билде (решение со 100 проектами проверяется 3-4 секунды). Для TeamCity можно даже публиковать статус с более удобном формате. Для всех остальных — придется читать Error Output. Приложение вернет 0, если все правила выполнились успешно. Итак, командная строка для TeamCity:

    SolutionCop.exe -s "C:\git\SolutionCop\src\SolutionCop.sln" -b TeamCity --suppress-success-status-message.

    Последний аргумент необходим в случае, если TeamCity не пишет статуса модульных тестов (несмотря на то, что они выполнялись позднее). Он отключит вывод SolutionCop для случая, если все правила выполнились.

Правила


Итак, SolutionCop настроен, он теперь заглядывает в решение, но ничего не проверяет. А потому перечислю правила, которые могут пригодиться. Детальное описание их использование есть на GitHub, я просто перечислю интересности. Для каждого правила, конечно же, можно задавать исключения и пр.
  • WarningLevel. Проверяет, что все проекты имеют Warning Level не ниже заданного.
  • TreatWarningsAsErrors. Смежное с предыдущим. Тоже синхронизует настройку компиляции.
  • TargetFrameworkVersion. Сихнронизует версию .Net для всех проектов
  • SuppressWarnings. Сихнронизует список предупреждений, на которые компилятор будет закрывать глаза.
  • FilesIncludedIntoProject. Проверяет, что все файлы, находящиеся в папке с проектом, включены в этот проект (дополнительно указыватся расширение). Невероятно полезное правило. Например, один раз при неправильном git merge у нас из проекта с модульными тестами выпало около трети файлов. Заметили это, конечно, не сразу, к тому времени мы упустили уже несколько багов. Также помогает с очисткой репозитороя, чтобы избежать кучи висящих файлов, которые никто не использует.
  • ReferenceNuGetPackagesOnly. Запрещает динамически линковаться на библиотеки, которые не добавлены как NuGet пакеты. По факту это исправление примера 2 выше.
  • NuGetPackagesUsage. Определяет Nuget пакеты, которые можно использовать. Эта настройка нужна для задач, когда в продукте есть несколько команд, у каждой свой репозиторий, они ссылаются друг на друга, а также на некоторые общие компоненты. И чтобы избежать проблем при объединении кода, можно определить общие правила для всех: какие версии пакетов кому можно использовать. В этом случае все правила хранятся отдельно от кода, в отдельном репозитории.
  • SameNuGetPackageVersions. Запрещает использование разных Nuget пакетов в решении. Исправление ошибки из примера 1.
  • NuGetAutomaticPackagesRestore. Проверяет, что все проекты восстанавливают Nuget пакеты в процессе сборки. Иначе разный порядок компиляции на CI сервере может привести к тому, что nuget пакеты не подгрузятся вовремя, так как какой-то проект пользовался тем, что его зависимости загружают другие проекты.


На деле, в SolutionCop есть и ряд других правил, которые помогут вычищать код. Я постарался перечислить те, которые могут понадобиться почти всем, кто разрабатывает на .Net.
А как вы автоматически проверяете свой код после коммита?

Проголосовало 59 человек. Воздержалось 43 человека.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Комментарии (20)


  1. Melz
    14.11.2015 18:27

    Выбран странный способ решения проблемы.

    1. Народ вот пишет собственный менеджер пакетов на F# Paket. Довольно успешно все это решает на корню.
    2. Порядок компиляции вроде можно менять и вроде даже через GUI.
    3. Вы получите 2 версии одной библиотеки после деплоя. Не очень хорошая идея.

    Он же может вытащить сорс библиотеки с GitHub.


    1. imanushin
      14.11.2015 20:48

      Хмм… У меня в статье речь не о том, чтобы сменить пакетный менеджер, а о том, чтобы проверять ряд вещей, где проверки nuget конфигурация — это просто пара пунктов. Ведь твой тул не сможет проверить, что, например, все файлы на диске включены в проект, верно?

      Про замену nuget — ок, я не исследовал этот вопрос.


      1. Melz
        14.11.2015 21:39

        Ну если я правильно понял, то пример 1 и 2 он покрывает. Если зависимость (т.е. библиотека) прописана правильно, то
        paket auto-restore
        восстановит ее. В вашем случает вместо Resharper импортом будет заниматься сам менеджер пакетов. Как, на пример, HTTP dependency. Ну или
        paket update [--force|-f]
        скачает и установит все заново.

        Хотя если делать просто, то можно в свойствах проекта просто прописать pre-build action и через xcopy гарантированно копировать файлы (он ругается если не находит).

        Например, один раз при неправильном git merge у нас из проекта с модульными тестами выпало около трети файлов. Заметили это, конечно, не сразу, к тому времени мы упустили уже несколько багов.

        Эту часть я не совсем понял. Почему при отсутствии файлов проект вообще прошел тесты и запустился на CI сервере. Даже если это только зеленые тесты существующей функциональности то как получилось что и ПМ и кодер пропустили эту часть.
        Что как бы говорит о мотивации в команде.
        Либо неправильных процессах. Т.к. тот кто последний использовал ныне не используемые файлы должен был их удалить.


        1. imanushin
          14.11.2015 21:49
          +1

          Не понимаю… Зачем приводить командные строки? Допустим, у нас проектов 50. В 30 их них есть пакет А. В 20 нет. И также допустим, что мы хотим начать использовать какой-то интерфейс из пакета А в одном из 20 проектов. В случае, когда нет ошибок программиста, надо всегда подумать и подключить dll правильно, с помощью командной строки: Install-Package для nuget или packet для другого пакетного менеджера. Это будет правильно, спору нет. Я же показываю ситуацию, когда dll подключена в обход менеджера пакетов, т.е. ошибка программиста. В случае SolutionCop система автоматом поймает такую ситуацию. А как ты рассказываешь, что надо опять человеку подстраховать (ага, в век автоматизации).

          Почему при отсутствии файлов проект вообще прошел тесты и запустился на CI сервере.

          В проекте, допустим, 300 файлов. После неправильного мержа пропало около 200. Так как это unit тесты, на них никто особо не ссылается, а значит в результате пропажа не видна после прогона тестов. merge проходит мимо code review, а потому получаем вот такую штуку. И как ПМ должен был не пропустить? Проверять каждый merge?


          1. Oxoron
            14.11.2015 22:47
            +1

            Занимаясь автоматизацией билдов, я проверял статусы пару раз каждый день, плюс регулярные проверки тим-лида. Потеря 200 файлов — это сокращение тогдашнего билда минут на 10, и уменьшение числа тестов. Так что ошибка обнаруживается за 4-8 часов.

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


          1. Melz
            14.11.2015 22:54

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

            Ошибка программиста это в первую очередь подключение dll в обход менеджера пакетов. Даже если оставить несчастный Paket в покое и у вас супе-пупер закрытые библиотеки для собственного пользования )) то всегда можно прописать собственные фиды в NuGet (https://docs.nuget.org/create/hosting-your-own-nuget-feeds), снять (myGet & Co) или даже поднять собственный NuGet сервер. И забыть про все проверки.
            а значит в результате пропажа не видна после прогона тестов.

            Пропавшие Unit будет видно как минимум на Code Coverage ;)
            merge проходит мимо code review, а потому получаем вот такую штуку. И как ПМ должен был не пропустить? Проверять каждый merge?

            Вот смотрите, кто-то (скорее всего ПМ) эти тесты заказал (ну не робот же) и скорее всего они ему нужны, раз уж ему выделили на это ресурсы (людей/часы).
            Перед началом каждой итерации/спринта/фазы/и тд. мы ставим себе цель и задаем метрики. В случае тестов это повышение качества и стабильности кода. Допустим покрыть тестами этот модуль, было 200 нужно 300.
            В конце итерации кто-то (ПМ) должен проверить сделана ли работа и что получилось. И подумать что делать дальше.
            Это его работа. Как докажете шефам что не устроили себе каникулы за счет работодателя?


            1. Lite
              14.11.2015 23:24
              +1

              Иногда DLL напрямую может подключить какой-нибудь «helper». Например автоимпорт R#. При код ревью должно вылезать, но csproj часто изучают поверхностно.


  1. IamKarlson
    14.11.2015 18:52
    +1

    меня вот кстати напрягает что нельзя пакет поставить сразу для солюшена. ну вот серьёзно. есть у меня какой-нибудь NLog (или log4net). я не хочу руками его ставить в каждый солюшен. никак нельзя его сразу всюду? ну только без советов в духе «достаете повершел»


    1. Melz
      14.11.2015 19:06
      +2

      Можно кликнуть правой по солюшену и выбрать «Manage NuGet packages for solution...». А там уже проставить чекбоксы.


    1. kenoma
      14.11.2015 19:06
      +2

      На уровне солюшена открываете управление пакетами NuGet и ставите галочки напротив всех проектов, куда надо этот пакет добавить. Делов то.


      1. IamKarlson
        15.11.2015 07:58

        Слушайте, вы мне прям жизнь спасли. Ведь она там и правда есть эта кнопка. А я её искал и не находил.


        1. kenoma
          15.11.2015 09:36

          Справедливости ради, Melz опередил меня с ответом на пару секунд.


  1. IamKarlson
    14.11.2015 18:53

    ну да, и было бы клева пакет с гитхаба подключать


  1. stepik777
    14.11.2015 19:09
    -3

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


    1. Melz
      14.11.2015 19:17
      +3

      Ну на самом деле довольно часто. Если проект большой то части пишутся как библиотеки и подключаются к гуи.
      Или допустим если разработка идет под разные платформы, логика выносится в общий проект. В Win8 такое было: телефон, планшет, десктоп.


    1. vyacheslav_ka
      14.11.2015 20:23
      +1

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


    1. Oxoron
      14.11.2015 22:32
      +1

      Самая элементарная вещь — инкапсуляция. Модификатор internal защищает код проекта от внешнего доступа. Если все проекты слить — защита падет. Размываются интерфейсы, увеличивается вероятность прострелить себе ногу.

      Чуть менее элементарная — конфликты слияния. Когда в один проект коммитят 20 разработчиков, при добавлении\переименовании\добавлении файлов конфликты слияния будут возникать чаще, чем при разработке «каждый разраб пилит свой проект». Каждый конфликт слияния — это потеря времени минимум одного человека.

      Есть еще переносимость. Если часть кода используют несколько приложений, разумнее выделить её в проект\сервис, поддерживать и изменять будет проще.

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

      Время сборки. Билд слитого-из-20 проекта может длиться пару минут, в то время как билд малого проекта — несколько секунд.

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

      Работать со слитым проектом можно, но это неудобнее, чем работать с 20 малыми проектами: больше времени на поиск ошибок, больше времени на тесты\билды, выше вероятность ошибки. Как верно заметили ниже, если проект небольшой — проблем нет. Если же проект кроссплатформенный\пишется большой командой\содержит много логики — стоит разбить его на проекты.


  1. stoune
    15.11.2015 00:07
    +1

    Я бы предпочёл чтобы работало например как плагин к SonarQube/SonarLint чем настраивать надцатый анализатор кода.


    1. Lite
      15.11.2015 14:54
      +2

      Добавляйте хотелки на GitHub


  1. Ch0bits
    16.11.2015 18:12
    +1

    Отличное решение! Спасибо вам большое. Прикрутил к своим проектам.
    Раньше такое геморрой был все эти проверки делать вручную. Особенно вечная проблема: у Васи собирается, а у Пети нет, потому что где-то закралась ссылка не в то место. А то что бывает устаревший AutomaticPackagesRestore, я даже не подозревал :)