stdray: (Default)
[personal profile] stdray
Часто слышу от своих знакомых, программирующих на C# и использующих ReSharper, что едва ли не самой полезной возможностью решарпера является проверка стиля кодирования. То есть решарпер проверяет правильность именования переменных, всякие там Pascal Casing для имен типов, Camel Casing для переменных и прочее такое. В этом есть смысл, поскольку многие программисты style guide не читают, а я читал, но почти ничего не помню. И тоже полагаюсь на ReSharper, хоть и регулярно возникают с ним разногласия.

А вот для Nemerle никакого решарпера нет, более того, нет никакого style guide. Размышляя над этим по дороге, я пришел к выводу, что второе существенно, а первое - не очень. Поскольку многое можно сделать на макросах. Например, захотел я удостовериться, что названия всех типов начинаются с большой буквы. Да еще чтоб я мог выбирать, что делать с несоответствием: выдавать ошибку компиляции, предупреждение или просто проинформировать. Несложно. Для этого надо лишь объявить макрос-атрибут уровня сборки

[MacroUsage(MacroPhase.BeforeInheritance, MacroTargets.Assembly)] \

macro CapitalTypeNames(alertType : string)

NameConventionImpl.CapitalTypeNames(Macros.Manager(), alertType)


А при его вызове, собрать типы, объявленные в сборке, отфильтровать имеющие некорректные названия и вывести информацию указанным образом.

public CapitalTypeNames(manager : ManagerClass, alertType : string) : void

def isIncorrect = t => t.Name |> fstChar >> Char.IsUpper >> !_

def msg = n => $"Incorrect type name '$n'. Type name must begin with a capital letter"

def showMsg = match(alertType.Trim().ToLower())

| "error" => (loc, name) => Message.FatalError(loc, name |> msg)

| "warning" => (loc, name) => Message.Warning(loc, name |> msg)

| "info" => (loc, name) => Message.Hint(loc, name |> msg)

| p =>

Message.FatalError($"Incorrect parameter $p to CapitalTypeNames macro."

+ "'Error', 'Warning' or 'Info' expected")

def alert = t => showMsg(t.Location, t.Name)

def types = manager.NameTree.NamespaceTree.GetTypeBuilders()

types.Where(isIncorrect).Iter(alert)


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

Затем, я подумал, что неплохо бы проверять, что названия публичных членов класса начинаются с большой буквы, а приватных - с маленькой. Вообще, это большая беда Nemerle, я часто вижу публичные поля вида some_field, иногда даже мутабельные. Это все создает впечатление неопрятности. Наверное, имеет смысл использовать соглашения из C#, тем более что одна из задач Nemerle - быть понятным для программиста на C#. C другой стороны, если вдуматься, то для того, чтобы сделать определенный член класса публичным, надо сперва пометить его модификатором public, затем еще разок подтвердить свое намерение, дав ему имя, начинающееся с большой буквы. Это двойная работа и лишний шум в коде. Можно сделать программирование чуть удобней, закодировав данное соглашение. То есть все члены класса с большой буквы становятся публичными, с мелкой - остаются приватными. Можназделоть. Пишу еще один макро-атрибут

[MacroUsage(MacroPhase.BeforeInheritance, MacroTargets.Assembly)] \

macro ModifiersByName()

NameConventionImpl.ModifiersByName(Macros.Manager())


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

public ModifiersByName(manager : ManagerClass) : void

def isPublic = fstChar >> Char.IsUpper;

def isPrivate = fstChar >> Char.IsLower;

def canChange = m => m.modifiers.Modifiers & NM.AccessModifiers == NM.None

def getModifier(member)

match(member.Name)

| n when isPublic(n) => NM.Public |> Some;

| n when isPrivate(n) => NM.Private |> Some;

| _ => None();

def setModifier(member)

when(member |> getModifier is Some(m))

member.modifiers.Modifiers |= m

def types = manager.NameTree.NamespaceTree.GetTypeBuilders();

def members = types.SelectMany(_.GetParsedMembers());

members.Where(canChange).Iter(setModifier)


Теперь можно обойтись без явной расстановки модификаторов доступа, либо использовать их для тех случаев, когда по каким-то причинам надо выйти за рамки выбранных соглашений.
БОГОСОГЛАШЕНИЯ
Работает, в том числе и для вложенных типов, а так же статических членов.

Полный код экспериментов на пастбине.

Я доволен. Какую-то часть работы, которую обычно выполняет IDE или даже платные плагины для IDE, можно переложить на макросы и компилятор. А можно пойти еще дальше, "материализовав" соглашения. При этом можно выработать свои соглашения для конкретного проекта. Я вполне допускаю, что в разных ситуациях (зависимости от задачи и числа разработчиков) разные соглашения будут более удобными. Можно разрабатывать под себя и гарантировать, что в рамках проекта эти соглашения выполняются. При этом все это выполняется при компиляции, через стандартное api компилятора, не требует IDE и дружит со средствами CI. Как-то так. Что скажете, имеет смысл рассматривать макросы как альтернативу IDE в подобных вопросах?

August 2017

S M T W T F S
  12345
6789101112
13141516171819
20212223242526
27282930 31  

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags