Заметки на полях

Мой дядя самых честных правил программ исходники за так ...

24.04.2009

Управление окнами с клавиатуры также как в Windows 7 и даже больше

Более полугода назад я стал обладателем хорошего широкого прямоугольного монитора LG W2600HP и избавился от надоевших квадратов, по крайней мере, дома.

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

А недавно обнаружил прекрасную утилиту keysextender от outcoldman, для управления окнами с помощью горячих клавиш Win + [Right|Up|Left|Down].

keysextender_vista

Win + Up – максимизация текущего окна,

Win + Down – минимизация текущего окна, или восстановление максимизированного,

Win + Right – поместить на полэкрана справа,

Win + Left – поместить на полэкрана слева.

Отличная, в общем, утилита, за исключением мигающего окошка при старте.

Двойная максимизация

С её помощью я решил автоматизировать управление окнами на своём мониторе, и реализовать дополнительную функцию двойной максимизации. Это когда по первому нажатию Win + Up не сразу максимизирует окно, а вначале помещает его определённый размер, а по второму делает честную максимизацию.

Так как проект Open Source, то сделать это не составило никакого труда и стоило пару часов кодирования и отладки.

Результат:

Binary: keysextender_withdoublemaximize_bin.zip,

Source: keysextender_withdoublemaximize_src.zip.

Патч отправлен автору и, возможно, фича попадёт в основную ветку проекта.

23.04.2009

История Microsoft в альтернативной трактовке

Прочитал довольно интересный конспирологический исторический рассказ об истории совместного бизнеса IBM и Microsoft: IBM - Microsoft: история "рокировки" лидеров компьютерной индустрии.

Факты не проверял, но читается легко и интересно.

“Факт, который почему-то никто не приводит - Гейтс не  разрабатывал интерпретатора бейсика!  Altair BASIC писал нанятый "им сокурсник по  гарварду - Monte Davidoff”.

“QDOS was based on Gary Kildall's CP/M, Paterson had bought a CP/M manual and used it as the basis to write his operating system in six weeks, QDOS was different enough from CP/M to be considered legal.

Microsoft bought the rights to QDOS for $50,000, keeping the IBM deal a secret from Seattle Computer Products. Gates then talked IBM into letting Microsoft retain the rights, to market MS DOS separate from the IBM PC project, Gates proceeded to make a fortune from the licensing of MS-DOS.”.

Вполне подходит для удовлетворения познавательного интереса к истории индустрии.

15.03.2009

Цикл как самый выразительный способ перебора элементов

С выходом C# 3.0 увеличилось количество способов перебрать элементы коллекции. Но всё же самым выразительным способом перебора элементов остался цикл.

Например, реализуем перебор всех свойств объекта и распечатаем те из них, у которых есть аттрибут DisplayNameAttribute.

using System;

using System.ComponentModel;

using System.Linq;

using System.Reflection;

 

namespace Home.Andir.Examples

{

    class Foo

    {

        [Category("Основные свойства")]

        [DisplayName("Свойство 1")]

        public string Property1 { get; set; }

 

        [Category("Основные свойства")]

        [DisplayName("Свойство 2")]

        public string Property2 { get; set; }

    }

 

    class Program

    {

        static void Main(string[] args)

        {

            Action<PropertyInfo, DisplayNameAttribute> PrintToConsole =

                (p, attr) => Console.WriteLine("\t{0} : {1}", p.Name, attr.DisplayName);

 

            Console.WriteLine("Foo properties (with foreach):");

            foreach (var p in typeof(Foo).GetProperties())

            {

                foreach (var attr in p.GetCustomAttributes(false))

                    if (attr is DisplayNameAttribute)

                        PrintToConsole(p, attr as DisplayNameAttribute);

            }

 

            Console.WriteLine("Foo properties (with foreach+linq 1):");

            foreach (var p in typeof(Foo).GetProperties())

            {

                var attributes = from attr in p.GetCustomAttributes(false)

                          where attr is DisplayNameAttribute

                          select attr as DisplayNameAttribute;

 

                attributes

                   .ToList()

                   .ForEach(attr => PrintToConsole(p, attr));

            }

 

            Console.WriteLine("Foo properties (with linq 1):");

            typeof(Foo).GetProperties()

                .ToList()

                .ForEach(p =>

                {

                    var attributes = from attr in p.GetCustomAttributes(false)

                              where attr is DisplayNameAttribute

                              select attr as DisplayNameAttribute;

 

                    attributes

                       .ToList()

                       .ForEach(attr => PrintToConsole(p, attr));

                });

 

            Console.WriteLine("Foo properties (with linq 2):");

            typeof(Foo).GetProperties()

                .ToList()

                .ForEach(p =>

                {

                    p.GetCustomAttributes(false)

                        .Where(attr => attr is DisplayNameAttribute)

                        .Cast<DisplayNameAttribute>()

                        .ToList()

                        .ForEach(attr => PrintToConsole(p, attr));

                });

 

            Console.ReadKey();

        }

    }

}

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

Не злоупотребляйте функциональным стилем в пользу читаемости! :-)

12.03.2009

Упрощённая валидация аргументов в .Net

Для валидации аргументов методов в .Net присутствует набор исключений, которые бросаются при нарушении контракта метода:

  • ArgumentException – обобщённое исключение предназначенное для наложения произвольных ограничений на аргументы метода,
  • ArgumentNullException – исключение, указывающее что значение аргумента оказалось равным null,
  • ArgumentOutOfRangeException – исключение, указывающее что значение аргумента выходит за пределы интервала разрешённых значений.

Одним из параметров конструктора этого типа исключений является имя аргумента, для которого проводится валидация:

void Foo(string fooArg)

{

    if (fooArg == null)

        throw new ArgumentNullException("fooArg");

 

    Console.WriteLine("Foo({0})", fooArg);

}

 

void Bar(int barArg)

{

    if (barArg < 0 || barArg > 10)

        throw new ArgumentOutOfRangeException("barArg");

 

    Console.WriteLine("Bar({0})", barArg);

}

Собственно, это всё довольно известно и часто используется.

Однако в таком коде присутствуют и свои, довольно очевидные, проблемы.

  • Отсутствует декларативность (явно используемые проверки с помощью if),
  • Использование строковых констант в качестве имён аргументов (мешает использованию автоматического рефакторинга Rename).

Попробуем избавится от этих проблем с помощью типизированного варианта получения имени аргумента. Для этого воспользуемся возможностями Linq Expressions. У меня получился следующий вариант:

Program.cs

using System;

 

namespace Home.Andir.Examples

{

    class Program

    {

        static void Main(string[] args)

        {

            var p = new Program();

 

            p.Foo(null);

            p.Bar(-1);

        }

 

        void Foo(string fooArg)

        {

            Argument.NotNull(() => fooArg);

 

            Console.WriteLine("Foo({0})", fooArg);

        }

 

        void Bar(int barArg)

        {

            Argument.InRange(() => barArg,

                new Range<int> { Begin = 0, End = 10 });

 

            Console.WriteLine("Bar({0})", barArg);

        }

    }

}

В коде явно прибавилось декларативности и исчезли проблемы со строковым именем проверяемого аргумента. А итоговая функциональность кода совсем не изменилась. Отлично.

Посмотрим на реализацию класса Argument.

Argument.cs

using System;

using System.Linq.Expressions;

 

namespace Home.Andir.Examples

{

    public static class Argument

    {

        private static string GetArgumentName<T>(Expression<Func<T>> argumentExpression)

        {

            var memberExpression = argumentExpression.Body as MemberExpression;

            if (memberExpression == null)

                throw new ArgumentException(

                    "Only MemberExpression allowed for expression body.",

                    "argumentExpression");

 

            return memberExpression.Member.Name;

        }

 

        private static T GetArgumentValue<T>(Expression<Func<T>> argumentExpression)

        {

            var compiledExpr = argumentExpression.Compile();

 

            return compiledExpr.Invoke();

        }

 

        public static void NotNull<T>(

            Expression<Func<T>> argumentExpression)

        {

            var argName = GetArgumentName(argumentExpression);

            var argValue = GetArgumentValue(argumentExpression);

 

            if (argValue == null)

                throw new ArgumentNullException(argName);

        }

 

        public static void InRange<T>(

            Expression<Func<T>> argumentExpression,

            Range<T> range)

            where T : IComparable<T>

        {

            var argName = GetArgumentName(argumentExpression);

            var argValue = GetArgumentValue(argumentExpression);

 

            if (argValue.CompareTo(range.Begin) < 0

                || argValue.CompareTo(range.End) > 0)

                throw new ArgumentOutOfRangeException(argName);

        }

    }

}

И небольшой класс для задания интервала Range.

Range.cs

using System;

 

namespace Home.Andir.Examples

{

    public class Range<T> where T : IComparable<T>

    {

        public T Begin { get; set; }

        public T End { get; set; }

    }

}

Реализация, конечно, получилась чисто игрушечная, но её вполне достаточно чтобы показать основную идею.

Такой вариант, конечно, не является идеальным, но позволяет используя только средства языка C# 3.0 упростить и улучшить механизм валидации аргументов.

А более красивый и практически идеальный вариант можно получить с помощью AOP. Для моего примера можно предположить такую реализацию:

void Foo([NotNull] string fooArg)

{

    Console.WriteLine("Foo({0})", fooArg);

}

 

void Bar([InRange(0, 10)] int barArg)

{

    Console.WriteLine("Bar({0})", barArg);

}

Здесь условия на значения аргументов накладываются с помощью атрибутов. Максимально декларативно и без потери читабельности.

На сегодняшний день подобный вариант можно организовать, например, с помощью PostSharp. В частности, стоит обратить внимание на проект ValidationAspects, который также использует PostSharp для реализации аспектов.

Похожий вариант валидации аргументов возможно будет организовывать и в будущей версии языка C# 4.0 (прочитать об этом можно здесь).

08.03.2009

JavaScript: Никогда не переопределяйте Object.prototype!!

Вот уже в который раз наблюдаю использование в коде сторонней библиотеки попытки сделать всё красиво за счёт добавления функций в Object.prototype.

    Object.prototype.Foo = function()

    {

        // Что-то делаем здесь.

    }

На первый взгляд может показаться, что нет ничего страшного в таком коде. Но, стоит только вспомнить один из самых распространённых вариантов использования объектов в JavaScript: “Object as hash-tables” и:

    var myHashTable = { "key1" : "value1", "key2" : "value2" };

 

    for (var key in myHashTable)

    {

        // используется функция логирования от расширения Firebug (http://getfirebug.org/).

        console.log("key = '" + key + "', value = '" + myHashTable[key] + "';");

    }

 

    // Вывод:

    // key = 'key1', value = 'value1';

    // key = 'key2', value = 'value2';

    // key = 'Foo', value = 'function () { }'; // WTF???

Упс. В импровизированной хэш-табличке, внезапно, появился новый забавный элемент.

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

Чтобы обнаружить этот баг на ранней стадии, имеет смысл добавить в свои unit-тесты следующий тест:

    testObjectAsHashTableAreEmptyByDefault = function()

    {

        var obj = {};

 

        var keysCount = 0;

        for (var key in obj) { keysCount++; }

 

        assertEquals(keysCount, 0);

    }

Разработчикам же, библиотек для JavaScript нелишне будет запомнить, что Object.prototype – это тайна за семью печатями (sealed).