Monday, October 24, 2011

Вопросы по курсу Технология проектирования

Модно нынче стало автоматическое тестирование... Начал вот вопросы писать для курса Технология проектирования ПО.

1.       Жизненный цикл программного проекта.
a.       Разработка, не работает, получили бабла, забили нафиг.
b.      Анализ, прототип, разработка, тестирование, внедрение.
c.       Договорились об откате, ничего не делаем, сдали что есть.
d.      Нашли дураков, перезаказали им, ждем бабла.
2.       Для чего предназначен этап анализа?
a.       Для проведения анализа.
b.      Для сдачи анализов.
c.       Пройти флюорографию.
3.       Для чего предназначен этап проектирования?
a.       Для обучения рисованию маркерами на доске.
b.      Для проектирования.
c.       Показать что мы знаем слово UML и это круто.
4.       Для чего предназначен этап тестирования?
a.       Для сдачи тестов (каждый в команде выбирает свой тест).
b.      Чтобы убедиться что ничего не работает.
c.       Чтобы убедиться что хоть что-то работает.
d.      Для понимания что же мы сделали.
5.       Для чего предназначен этап внедрения?
a.       Для внедрения шпионов и жуков заказчику.
b.      Для поиска нефти  в недрах.
c.       В недрах тундры выдры в гетрах тырят в вёдра ядра кедров.
d.      Для установки сделанного заказчику.
6.       Для чего предназначен этап сопровождения?
a.       Чтобы заказчик не отвертелся.
b.      Чтобы ничего не упало.
c.       Чтобы еще напилить денег.
7.       Что такое проект?
a.       Договоренность с заказчиком о получении денег
b.      Договоренность с заказчиком о решении его проблемы за деньги
c.       Сговор сотрудников, желающих заняться программированием
8.       Кто такой менеджер проекта? Чем он занимается?
a.       Пьет кофе и говорит по телефону
b.      Выбивает деньги и потом их же и получает
c.       Руководитель команды
d.      Если не можешь остановить бардак, возглавь его

Стиль кодирования

Что такое стиль кодирования
Конечно, все из вас читают книги, газеты и журналы. Что важно для понимания сути текста или статьи? Наверное, в первую очередь, это понятность изложения (конечно, я не рассматриваю случай, когда статья написана на незнакомом вам языке или в ней изложен материал из совсем не знакомой вам области). То же самое касается и устной речи. Путанный, сбивчивый рассказ очень сложно понять. Бывает и так, что понять о чем хотел сказать автор и вовсе не получается и хорошо, если он близко и можно его переспросить.

Наверное, все в школе писали сочинения. Так вот, сочинение, или, другими словами, изложение своих мыслей на бумаге, ничем не отличается от написания кода! Код – это такое же изложение мыслей, только вместо букв русского языка используются специальные коды. Ведь даже название соответствует – язык программирования.

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

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

Правила написания кода называются стилем кодирования (code style). О нем я и хочу поговорить в этой лекции.

Почему важно писать код понятно


Конечно, вы можете сказать – ну а зачем мне надо писать код понятно? Разумеется, это "надо" зависит от ваших целей. Если вы пишите текст для себя, а книги "в стол" и никому не собираетесь их показывать – пишите как хотите, тут правила не нужны. Если же вы рассчитываете на продажу своих книг и всемирное признание – пишите так, чтобы читатель мог вас понять и оценить ваш талант.

Если вы пишите код для себя, просто из интереса – пишите как хотите, лишь бы вам было приятно. Если же вы работаете в команде, то пишите по правилам. Почему? Представьте, что вы написали насколько удивительный и прекрасный код, что на его понимание вашему коллеге потребовалось полчаса времени. А в проекте работает 10 человек. И вот на один кусочек кода 300 минут рабочего времени улетело в трубу. Еще один такой кусочек – еще столько же. А в рамках большого проекта эта цифра вырастает в дни и недели, и прибыль, как я рассказывал, утекает прямо из рук. Причем ничем не обоснованно. Просто вы так написали код.

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

Очень хочу чтобы вы поняли меня правильно. Я не говорю, что нужно писать такие примитивные книги, чтобы их понял кто угодно, даже тот, кто не в теме. Я не говорю, что нужно писать такой код, чтобы было понятно даже школьнику. Вовсе нет. Я надеюсь, вы понимаете, чем отличается "примитивно" от "понятно" и "интересно". С кодом то же самое: код должен быть понятен и прост, но не примитивен.

Sunday, October 2, 2011

C++ на первом курсе это жестоко

После перерыва в 6 лет снова начал писать на C++ (раньше писал на нем больше 10 лет). После C# впечатление шага назад.

Одно не пойму - зачем на 1 курсе давать C++. Язык сложный, очень гибкий...

На C# я могу объяснить, что вывод на консоль это Console.WriteLine. А на C++ мне надо объяснять что такое using namespace, что такое include и т.д. и т.п. И дальше выбор - или идти с cout и объяснять модификаторы, либо с printf, что ничем не проще.

А ввод с консоли это вообще отдельная песня...

Или вот вызывают pow(i, 2). Если i типа int, то компилятор говорит что не может выбрать метод между параметрами double и float. Студенты от этой ошибки просто шалеют. Или pow(i, 1/3) - тут другая радость. Разумеется что 1/3 приводится к целому и ничего не работает. Объясняю что надо писать (float)1/3, чтобы им было понятно что это дробь и компилятору тоже. Мне это понятно, но человеку только пришедшему... хорошо если с pascal в запасе... вот так вот по голове...

Рассказал что if (a=1) это типовая ошибка. Что & и && это разные опреаторы. Разумеется не помогает. Все равно ошибаются.

Точки-с-запятой и скобки

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

if (a == b);
{
  // выполнится не зависимо от условия
}

Или так:

for (int i=0; i<10; i++);
{
   // выполнится один раз, без цикла
}

Ну и со скобками в блоках конечно тоже беда:

for (int i=0; i<10; i++)
  a++;
  b++;

К сожалению, выравнивания еще мало - надо бы еще скобки блока поставить. Иначе b++ в цикл не войдет.

Особенности сравнения float.NaN

float.NaN - специальное обозначение бесконечности. Но нужно помнить, что две бесконечности не равны по определению. Да и по спецификации тоже.
Т.е.
 float f = float.NaN;
 if (f == float.NaN)
 {
    // никогда не выполнится
 }

Для правильного сравнения используйте float.IsNaN(f).

Friday, September 2, 2011

Быстрое сохранение в XML класса Font, Color, enum...

Вот лень мне было по кусочкам Font сохранять в XML. Нашел вот такой способ:

public static XmlElement AddElementByConverter<T>(XmlElement parentNode, string nodeName, T attrValue)
{
  TypeConverter converter = TypeDescriptor.GetConverter(typeof(T));
  string value = converter.ConvertToString(attrValue));
  .... ну и тут дальше как обычно
}


И загружать просто:

public static T ReadByConverter<T>(XmlNode element, string path, T defaultValue)
{
   T nodeValue = defaultValue;
   XmlNode node = element.SelectSingleNode(path);

   .........
   TypeConverter converter = TypeDescriptor.GetConverter(typeof(T));
   nodeValue = (T) converter.ConvertFromString(valueString);
   if (nodeValue == null)
      nodeValue = defaultValue;
   .........
   return nodeValue;
}


Такая конструкция замечательно глотает и Font и Color и любой enum.
Типа кодюлька номер раз :)

Название

Подумал, если короткие фразы, это "коротюльки", то короткие кусочки кода должны бы называться "кодюльки". Может книгу надо было назвать не Сборник рецептов, а Сборник кодюлек. Ну или как-то так... :)

Thursday, September 1, 2011

Интересная штука с загрузкой изображений

Пока даже не очень понял в чем тут проблема, но нашел как решить.
Имею Image, который сохраняю в файл и загружаю из файла.

Сохраняю:

using (MemoryStream ms = new MemoryStream())
{
 this.bitmap.Save(ms, ImageFormat.Bmp); 
 byte[] bitmapData = ms.ToArray(); 
 string imgBase64 = Convert.ToBase64String(bitmapData);
 xmlImage.InnerText = imgBase64;
}


Загружаю:

string imgBase64 = xmlImage.InnerText;
if (!string.IsNullOrEmpty(imgBase64)) 
  byte[] bitmapData = Convert.FromBase64String(imgBase64); 
using (MemoryStream ms = new MemoryStream(bitmapData))   
  this.bitmap = Image.FromStream(ms) as Bitmap
;

Все работает без проблем - bitmap отображается как нужно, размеры сохранены
и вообще все отлично.

Но если попытаться сохранить этот загруженный image еще раз,
то получаю ошибку:
        A generic error occurred in GDI+.
Что сие значит не понятно.

Решил проблему просто, но не понятно - либо не закрывать поток,
либо создавать новый image, соответственно с новым потоком.

Т.е. либо так:

  MemoryStream ms = new MemoryStream(bitmapData); 
  this.bitmap = Image.FromStream(ms) as Bitmap;
   

Либо так:

  this.bitmap = new Bitmap(Image.FromStream(ms) as Bitmap);

Первый вариант мне не нравится, прямо душу режет.
Второй еще пережить можно, хотя не понятно почему и в чем проблема.

UPD: Т.е. получается что при выполнении FromStream используется тот же буфер, что
я создавал в потоке.

UPD: Интересно этот редактор параграфы переставляет. Вроде сохраняешь
нормально, а получается такая каша... Хоть бы пофиксили. Задолбало.

Tuesday, August 30, 2011

Adobe

Люблю такие продукты... Хочу поставить триал Adobe Illustrator. Захожу на сайт, нашел кнопоку скачать, жму... Скачивается программа скачивания триала. Всего-то 3 Мб. Запускаю - она говорит что ей надо чтобы был установлен Adobe IAR. Сама при этом скачивает его и не собирается - вываливат обычный message box - типа тебе надо, ты и качай. И это только чтобы запустить скачивание триала. Ну мы не гордые, закачал его. Думаете все? Неа. Надо еще завести аккаунт на Adobe и получить Adobe ID. Вот тогда мне дадут скачать триал.

Вот так все круто и очень любезно для пользователей.

Tuesday, August 9, 2011

AssemblyResolve и локализация

В .NET 4.0 имеется небольшая, но не приятная проблема - обработчик загрузки модулей AsseblyResolve теперь вызывается, если не найден файл с ресурсами локализации xxx.resources.dll.

Проблема возникает при переходе с 2.0 на 4.0 для тех, кто генерил исключения в AssemblyResolve, если файл не найден. Правильнее возвращать null - тогда падать не будет. Иначе ждет сюрприз...

Wednesday, July 27, 2011

Как правильно подключить обработчик AssemblyResolve в WinForms

Возник интересный вопрос – в какой момент правильнее подключить AssemblyResolve, который, как известно, позволяет подложить нужные dll, если система сама их не нашла.
Вариант 1 – делаем в конструкторе основной формы
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
            AppDomain.CurrentDomain.AssemblyResolve +=
                        new ResolveEventHandler(CurrentDomain_AssemblyResolve);
Этот вариант будет работать только в том случае, если форма не использует искомые dll для UI, т.е. конструктор успеет вызваться. Для подключения таким образом UI-компонентов этот вариант не работает.
Вариант 2 – в методе Main
Этот вариант работает, если нужные dll не должны сразу загрузиться в домент. Даже если главная форма использует dll для построения UI, то это будет работать. Если же dll нужны для создания Application, то и этот вариант не сработает.
Вариант 3 – самый надежный, статический конструктор Program
Создаем статический конструктор для основного класса.
    static class Program
    {
        static Program()
        {
            AppDomain.CurrentDomain.AssemblyResolve +=
                 new ResolveEventHandler(CurrentDomain_AssemblyResolve);
            Application.SetUnhandledExceptionMode(
                 UnhandledExceptionMode.CatchException);
        }
Теперь обработчик будет гарантированно создан.

Thursday, May 19, 2011

Программирование это...

Программирование это творчество или бизнес? Вроде творчество, но мы хотим за него получать денег... Вроде бизнес, но мы говорим что сегодня нет настроения :) Не прет...

Я сравнивал изучение программирования с обучением плаванию - пока сам не попробуешь, ничего не получится. С бортика учить не выйдет, по книгам тоже. Только пробовать.

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

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

Так творчество или бизнес?...

Wednesday, May 4, 2011

Зачем переписывать у соседа код?

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

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

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

В той же качалке - берем сразу огромный вес, который придавит. Зовем здоровенного качка, просим "подними за меня, я не могу". Ну он-то понятно поднимет, может даже и не вспотеет, здоровый он. А вам-то что с этого толку? Даже морального удовольствия нет...

Tuesday, May 3, 2011

Цитата

Если спросите, то будете дураком 5 минут, а если не спросите, то всю жизнь

Thursday, March 17, 2011

Снова про code style...

Еще раз хочу про стиль кода написать. Замучился доказывать, что писать надо в едином стиле. Вот если кино начинается комедией, а заканчивается детективом, все плюются… Если в часть главы в книге написано от первого лица, а потом резко так, без перехода от третьего – странно все это выглядит. Возникает вопрос что курил автор. А если код так писать, почему-то нормально.
Второй вопрос – заглавные буквы. Заглавная буква это начало мысли, начало чего-то большого, обозначение идеи. Название городов пишут с большой буквы, имена людей и т.д. Но если взять и Просто так начать Писать часть слов Не понятно зачем писать с Большой буквы, то читать это сложно. Ну и про автора такого текста мысли какие-то бродят.. Так почему нужно писать имена переменных именно так:
  private int MyTempVar;
Почему с большой буквы-то? Имя метода (имя человека, города) – это понятно. А тут-то зачем? И читать такой код сложно, хотя можно конечно.
Вообще загадочно, почему то что в обычной жизни никто не делает, в программировании считается нормальным и до хрипоты спорят что можно и так.

Friday, March 4, 2011

Диалоги

Индус (и) разговаривает с боссом (б) в европейской фирме:
б: Is it done?
и: Yes, but not yet.

б: so did you have chance to complete it?
и: yes
б: could you please send it to me
и: I will send it to you tomorrow after it will be finally completed

- Дим, ты все сделал по этой задаче?
- Да, все
- Совсем все или еще что-то осталось?
- Ну тут еще немного...

События


События (event) это почти делегаты, только используется ключевое слово event, что позволяет редактору Visual Studio правильно распределять их по закладкам окна свойств.

Описание класса, использующего события:
namespace EventTest
{
    /// <summary>
    /// Тип метода
    /// </summary>
    public delegate void Progress(int progress);

    public class Parser
    {
        /// <summary>
        /// Описываем событие
        /// </summary>
        public event Progress ProgressHandler;

        /// <summary>
        /// Основной метод
        /// </summary>
        public void Calc()
        {
            for (int i = 0; i < 100; i++)
            {
                // Если обработчик задан - вызываем его
                if (ProgressHandler != null)
                    ProgressHandler(i+1);
            }
        }
    }
}

Использование этого класса:
namespace EventTest
{
    class Program
    {
        static void Main(string[] args)
        {
            // Создаем экземпляр
            Parser parser = new Parser();
            // Вешаем обработчик
            parser.ProgressHandler += new Progress(parser_ProgressHandler);
            // Можно и два метода обработки одного события
            parser.ProgressHandler+=new Progress(parser_ProgressHandlerSecond);
            // Можно описывать обработчики "по месту", хотя это не всегда удобно
            parser.ProgressHandler+= delegate(int progress)
            {
                Console.WriteLine("inline:" + progress);
            };

            // Вызываем метод
            parser.Calc();
        }

        /// <summary>
        /// Этот метод будет вызываться при обработке события
        /// </summary>
        static void parser_ProgressHandler(int progress)
        {
            Console.WriteLine("first:" + progress);
        }

        /// <summary>
        /// Можно вешать и два метода
        /// </summary>
        static void parser_ProgressHandlerSecond(int progress)
        {
            Console.WriteLine("second:" + progress);
        }
    }
}

Saturday, February 26, 2011

Подклассы или типы

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

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

public Color Foreground()
{
   swith (type)
   {
     case StoneType.One: return Colors.Red;
   ............
}

Либо вообще вынести эти параметры в конструктор и типы не заводить:

public Stone(Color foreground, int countStrike)
{
  .....
}

А вот если отличий будет очень много, то класс будет замусорен кучей if или switch. Вот тогда имеет смысл делать подклассы и разносить логику и код. Разумеется, если какой-то из "типов" имеет дополнительные свойства, то это однозначно нужно выностить в подкласс.

Monday, February 21, 2011

Еще пример про делегаты

Задача такая: по имени функции (математической) получить результат ее вычисления. Самый простой вариант такой:
public class CallFunc1
{
    /// <summary>
    /// Вызываем функцию по имени
    /// </summary>
    public double GetFuncValue(string funcName, double arg)
    {
        switch (funcName.ToUpper())
        {
            case "SIN": return Math.Sin(arg);
            case "COS": return Math.Cos(arg);
            case "TAN": return Math.Tan(arg);
            case "SQRT": return Math.Sqrt(arg);
        }

        throw new ArgumentException("Такой функции нет в списке зарегистрированных функций", funcName);
    }
}

Но тут мы вспоминаем, что квадратный корень нельзя вычислять от отрицательного значения и добавляем еще код:
case "SQRT":
    if (arg < 0)
        throw new ArgumentException("Попытка вычислить квадратный корень от отрицательного числа", "arg");
    return Math.Sqrt(arg);

Потом появляются еще функции и свои проверки… В результате метод GetFuncValue напоминает сборную солянку. А хотелось бы как-то попроще и покрасивее сделать.
С помощью делегатов можно сделать так. Описываем делегат, имеющий сигнатуру метода с одним параметром:
        // Делегат- математический метод
        public delegate double MathFunction(double arg);

А теперь описываем словарь соответствия названий и делегатов таких методов:
        // Список функций
        private Dictionary<string, MathFunction> funcList;

Инициализировать его нужно будет в конструкторе:
    /// <summary>
    /// Конструктор
    /// </summary>
    public CallFunc1()
    {
        // Создаем список функций
        funcList = new Dictionary<string, MathFunction>();
        funcList.Add("SIN", delegate(double arg) {
            return Math.Sin(arg);
        });
        funcList.Add("COS", delegate(double arg)
        {
            return Math.Cos(arg);
        });
        funcList.Add("TAN", delegate(double arg)
        {
            return Math.Tan(arg);
        });
        funcList.Add("SQRT", delegate(double arg)
        {
            if (arg < 0)
                throw new ArgumentException("Попытка вычислить квадратный корень от отрицательного числа", "arg");
            return Math.Sqrt(arg);
        });
    }

Обратите внимание, что реализация делегата сделана прямо “по месту” (это называется анонимными методами – методами без имени).
А с помощью LINQ вызов станет совсем простым делом:
    public double GetFuncValue(string funcName, double arg)
    {
        // Ищем нужную функцию в списке
        var func = funcList.Where(f => f.Key == funcName).Select(f => f.Value).FirstOrDefault();
        // Не нашли - генерируем исключение
        if (func == null)
            throw new ArgumentException("Такой функции нет в списке зарегистрированных функций", funcName);
        // Нашли - вызываем эту функцию
        return func(arg);
    }

Теперь каждое имя функции связанно со своей реализацией и своими ограничениями.