xeno_by (xeno_by) wrote,
xeno_by
xeno_by

Саммари практического опыта

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

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

Несколько поинтов ниже - это, по сути, экстракт самых важных мыслей из knowledge base, которую я веду в ВанНоуте. После некоторого размышления я решил запостить ее в паблик - так что, если интересно, можно глянуть: kb по общим вопросам, kb по вопросам архитектуры enterprise программ. Дисклеймер все тот же - текст может быть очень наивным, может тупо не соответствовать истине, за фидбэк скажу спасибо, ругательства не по сути не приветствуются.

Новое для меня отношение к программированию

Я очень рад, что когда-то давно, выбирая между плюсами и дотнетом, я непроизвольно сделал выбор в сторону дотнета. Концепция виртуалки, ила и джита - абсолютно винрарное комбо. Оно делает возможными кучу динамических техник, которые непрактично юзать на плюсах. А это я считаю - будущее программирования. Например, выводить проверки безопасности из статического и динамического анализа кода. Или же профилировать самого себя и по статистике исполнения перегенеривать себя для оптимизации под текущий инвайронмент. Или же обеспечивать транспарентную персистентность не тупым и явным ОРМом, а анализом использований объектного графа доменных сущностей и автоматическим префетчингом связей и коллекций. Тут бездна возможностей.

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

На своем опыте я убедился, что такие идеи ОЧЕНЬ упрощают разработку корпоративного ПО. И они не являются просто мертвыми технологиями ради технологий или ради того, что я недавно почитал научную работу X или увидел фреймворк Y. А почему я щяс могу их генерировать пачками? Я ведь далеко не гений, а просто знаю пару общих вещей, которые служат мне фундаментом. Поразительно, но этого достаточно, чтобы придумывать широчайший спектр идей во всех областях программирования.

Потому что я знаю, что:
  * Моя программа это не бездушный и низкоуровневый ассемблер,
  * Поэтому я могу ее в любой момент декомпилировать и проанализировать,
  * Поэтому я могу в любой момент сгенерировать еще кодярника и вставить его в работающую систему.

Да, все это возможно и в плюсах и даже в голом си. Но это так непрактично сложно и неприятно писать руками - просто prohibitively complex. Вот почему хорошая система программирования должна быть достаточно высокоуровневой, чтобы энаблить богатый образ мышления. И вот поэтому я люблю .NET, даже несмотря на то, что 1-2-3 в нем реализуются через жуткую задницу.

Новое для меня видение кодирования

Раньше мое представление хорошего кода было весьма абстрактным и основывалось на общепринятых гайдлайнах. В спринге пишут километровые xml с конфигурацией объектов прилаги - окей, это хороший код. Для вставки в прилагу новой формы нужно определить три класса, два xml-а и зарегать все в конфиге - окей, это хороший код. Ну и так далее.

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

Поэтому в своих проектах я стараюсь: 1) четко определить минимальные границы, в рамках которых может быть выражена идея, 2) задокументировать эти границы в ассертах, чтобы не забыть о них через несколько недель после написания, 3) писать минимальное количество интеграционных тестов, проверяющих корректность функционала (никакой речи не идет ни о TDD, ни о BDD, ни о стопроцентном покрытии - просто прекрасно эту мысль выражает Аенде в посте "Even tests have got to justify themselves").

***

Особенно хочу подчеркнуть важность ассертов, которые убивают сразу несколько зайцев - 1) вместе с каментами (о Боже, он пишет каменты к коду!) рассказывают о мыслях афтара кода, 2) предоставляют программисту легкий способ задокументировать возможные исключительные ситуации в программе (чем больше времени проходит между написанием кода и документацией его возможных крэшей, тем больше будет ошибок в этом процессе; ассерты здесь хороши тем, что легкостью своего написания они поощряют программиста прямо на месте предусмотреть возможные ошибки), 3) при нетривиальных ошибках точно указывают причину, по которой ошибка произошла, 4) в силу пункта #3 являются зародышами качественной обработки и представления ошибок. Вот пример кода с ассертами:

var module = t.Get("m_module").AssertCast<ModuleBuilder>();
var moduleIsCapableOfEmittingDebugInfo = module.GetSymWriter() != null;
emitDebugInfo.AssertImplies(moduleIsCapableOfEmittingDebugInfo);

Такая реализация ассертов (это третья или четвертая моя попытка) мне весьма нравится. Ассерты, объявленные в fluent-стиле в виде икстеншн-методов 1) элементарны в использовании, 2) не прерывают цепочку выражений, 3) не прерывают мысль, будучи объявляемы прямо на месте, 4) привносят минимальный синтаксический шум в кодярник. К сожалению, как бы ни был хорош статический чекер КодеКонтрактсов, я его не буду юзать исключительно по причине шумности и отвлеченности от основного кода.

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

Свой собственный словарик выразительных функций

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

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

Пример. В дотнете нет вариадик темплейтов, поэтому реализовывать таплы (tuples) очень невесело. Как способ реализации таплов, которые бы работали для любого числа параметров, а не как Action или Func, команда CLR предлагает следующий вариант: если тапл содержит более семи итемов, то он выражается таплом из 8 итемов - первые семь итемов обычные, а восьмой рекурсивно упаковывает остаток в еще один тапл. Вот как я реализовал эту логику (обратите внимание на кастомные ФВП, без которых код бы был более толстым и менее понятным):
Copy Source | Copy HTML
public static ITuple New(Object[] items, Type[] types)
{
    Func<IEnumerable<Tuple<Object, Type>>, ITuple> mkTuple = itemSpecs =>
    {
        var factories = typeof(Tuple).GetMethods(BF.PublicStatic).Where(m => m.IsGenericMethod);
        var factory = factories.Single(m => m.GetParameters().Count() == itemSpecs.Count());
        factory = factory.XMakeGenericMethod(itemSpecs.Unzip().Item2);
        return factory.Invoke(null, itemSpecs.Unzip().Item1.ToArray()).AssertCast<ITuple>();
    };
 
    // damn... that's why we need type inference to use functional style
    // upd. with introduction of "types" parameter it only became worse...
    return items.Zip(types).ChopEvery(7).FoldRight((ITuple tuple, IEnumerable<Tuple<Object, Type>> slice) =>
        mkTuple(tuple == null ? slice : slice.Concat(Tuple.New<Object, Type>(tuple, tuple.GetType()))));
}

К сожалению, практическое использование такого подхода зачастую омрачается ограниченностью сишарпа и связанного стека тулов: 1) очень неслабо раздувается список доступных методов, что ударяет по перфомансу интеллисенса студии и решарпера, 2) так как в сишарпе нет мощного тайп-инференса, то обычно приходится объявлять пачку оверлоадов: для Action, для Action<T1>, для Action<T1, T2>, для Action<T1, T2, T3>, ну и так далее пока не надоест, а еще же есть и Func..., 3) пункт #2 еще больше раздувает список методов, 4) иногда для того, чтобы удовлетворить строгую проверку типов сишарпа, приходится объявлять сложные лексические конечные автоматы (термин я выдумал сам - не ищите в нем скрытого смысла), 5) также напрягает отсутствие нативной поддержки многих элементов функционального программирования - богатого тайп-инференса, паттерн-матчинга, удобных таплов и так далее.
Tags: confession2009, dreams2010
Subscribe
  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic
  • 1 comment