Tag Archives: розробка

Проста схема перетворення інтерактивної процедурної програми з goto в функціональну рекурсивну

Власне вся передмова помістилась в заголовок. Хоча може для цього “паттерну” є коротша назва.

Існує клас інтерактивних програм які очікують вводу користувача, потім залежно від того вводу щось роблять, потім знову очікують вводу і так далі. Наприклад якась така програма “вгадай число”:

import random

def game():
    print('Як тебе звуть?')
    user = input()

    print('Привіт,', user)
    print('Давай пограємо гру відгадай число?')

    while True:
        number = random.randint(1, 10)
        print('Я загадав число від 1 до 10.')
        print('Спробуєш вгадати?')

        while True:
            print('Вводь свій варіант:')
            guess = input()
            if guess == number:
                print('Ого, так швидко вгадав. Грати ще раз?')
                while True:
                    answer = input()
                    if answer in ('так', 'ні'):
                        break
                    print('Ну то так чи ні?');

                if (answer == 'ні'):
                    print('Ну тоді бувай!')
                    return
                if (answer == 'так'):
                    break
            else:
                print('Ні, моє число ' +
                    ('більше' if (scope.guess < scope.number) else 'менше')
                    + ', пробуй ще'
                )
game()

JavaScript має з такими програмами проблему, бо він ніколи не зупиняється очікуючи на ввід користувача (якщо не рахувати функції alert() та компанії, він викликає функції які обробляють події, як от подію вводу. Щоб написати подібну програму для браузера, ми повинні реалізувати в ньому щось на зразок “консолі”:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
</head>
<body>
    <pre id="console"></pre>
    <input type="text" id="input" />

<script>
(function() {
    var input = document.getElementById('input');
    var output = document.getElementById('console');

    var write = function(text) {
        // Write text to console
        output.innerHTML += text;
    };
    var writeln = function(text) {
        // Write line to console
        write(text + 'n');
    };
    
    // Register default callback of echoing input
    var input_callbacks = [function(input) {
        writeln('> ' + input);
    }];

    var on_input = function(callback) {
        // Register input callback

        input_callbacks[1] = callback;
    }

    input.onkeyup = function(e) {
        // Call all callbacks on input
        if (e.keyCode == 13) { // Enter pressed
            for(var i=0; i < input_callbacks.length; i++) {
                input_callbacks[i](input.value);
            }
            input.value = '';
        }
    };

    // Module exports:
    window.CLI = {
        write: write,
        writeln: writeln,
        on_input: on_input
    };
}());
</script>

<script>
// Ну а тут буде наша програма для "консолі". 
</script>
</body>
</html>

Ми маємо модуль CLI, який містить три функції. write та writeln дописують до елемента “консоль” переданий їм текст, а функція передана в on_input, буде викликатись отримуючи вміст елемента input коли в ньому натиснуть Enter. Проблема якраз в тому що буде викликатись окрема функція, якій передадуть ввід, і нема функції яка б той ввід просто повернула. Як написати для такої консолі інтерактивну гру “вгадай число”?

Тут на допомогу може прийти цікаве визначення алгоритму, яке я знайшов в книжці “Мистецтво програмування” Дональда Кнута (ні, я її не осилив, не зміг запам’ятати набір інструкцій MIX, хоча може варто пошукати якийсь емулятор цієї машини, щоб та книжка веселіше проходилась).

Так от, Кнут пише що будь-яку програму можна задати фунцією P(step, scope), яка приймає набір аргументів scope (що може бути одним аргументом – словником). Таким чином ми уникаємо зміни стану і оператора присвоєння, викликаючи функцію рекурсивно з новини аргументами.

Тому наприклад таку програму написану на бейсікоподібному псевдокоді:

    i = 0
1:  writeln i
    i = i + 1
    goto 1

Можна переписати на JavaScript рекурсивно так:

    function exec(step, scope) {
        CLI.writeln(scope.i);
        exec(step, {i: scope.i+1});
    }

    exec(1, {x: 0});

Варто також зауважити що JavaScript не має оптимізації хвостової рекурсії, тому така програма в Firefox зупиняється вивівши число 11085, і пожалівшись в консоль повідомленням “too much recursion”.

Тобто правило таке – як тільки ми хочемо змінити стан – ми викликаємо нашу функцію яка задає програму передавши їй цей новий стан. Ми також можемо робити умовні і безумовні переходи, викликаючи нашу функцію з різними значеннями параметра step. Подивимось як ми зможемо переписати програму гри “вгадай число”, яку описали вище на JavaScript. Але для цього спершу перепишемо її на наш діалект BASIC:

1:  writeln 'Як тебе звуть?'
    input user
2:  writeln 'Привіт,' + user
    writeln 'Давай пограємо гру "Відгадай число"?'
3:  writeln 'Я загадав число від 1 до 10.'
    number = randint(1, 10)
    writeln 'Спробуєш вгадати?'
4:  writeln 'Вводь свій варіант:'
    input guess
5:  if guess == number then 9
6:  if guess < number then 8
7:  writeln 'Ні, моє число менше, пробуй ще' 
    goto 4
8:  writeln 'Ні, моє число більше, пробуй ще'
    goto 4
9:  writeln 'Ого, так швидко вгадав. Грати ще раз?')
    input answer
10: if anwser == 'так' then 3
11: if answer == 'ні' then 13
12: writeln 'Ну то так чи ні?'
    input answer
    goto 10
13: writeln 'Ну тоді бувай!'

Тепер переписуємо цю програму на JavaScript за такими правилами:

  1. Кожен набір операторів що починається міткою ми записуємо в блок:
    if (step == 1 /*мітка*/) {
        // Код сюди
        return;
    };
    
  2. Кожне присвоєння відбувається в scope:
    if (...) {
        scope.number = Math.floor(Math.random() * 10) + 1;
        ...
        return;
    };
    
  3. Кожен блок що закінчується goto закінчується викликом exec:
    if (...) {
        // Код сюди
        exec(4 /* куди послало goto */, scope);
        return;
    };
    
  4. Кожен блок що закінчується if then:
    if (...) {
        // Код сюди
        if (...) {
            exec(3 /* куди послав then */, scope);
        } else {
            exec(11 /* перехід до наступного блоку */, scope);
        }
        return;
    };
    
  5. Кожен блок що закінчується input закінчується викликом exec для наступного кроку в callback:
    if (...) {
        // код сюди
        CLI.on_input(function(input) {
            scope.guess = input;
            exec(5, scope);
        });
        return;
    }
    
  6. Програма починається без змінних і з першого кроку:
    exec(1, {});
    

Таким чином з “бейсіка” на JavaScript програму можна переписати так:

function exec(step, scope) {
    // 1:  writeln 'Як тебе звуть?'
    //     input user
    if (step == 1) {
        CLI.writeln('Як тебе звуть?');
        CLI.on_input(function(input) {
            scope.user = input;
            exec(2, scope);
        });
        return;
    };
    // 2:  writeln 'Привіт,' + user
    //     writeln 'Давай пограємо гру "Відгадай число"?'
    if (step == 2) {
        CLI.writeln('Привіт, ' + scope.user);
        CLI.writeln('Давай пограємо гру "Відгадай число"?');
        exec(3, scope);
        return;
    };
    // 3:  writeln 'Я загадав число від 1 до 10.'
    //     number = randint(1, 10)
    //     writeln 'Спробуєш вгадати?'
    if (step == 3) {
        CLI.writeln('Я загадав число від 1 до 10.');
        scope.number = Math.floor(Math.random() * 10) + 1;
        CLI.writeln('Спробуєш вгадати?');
        exec(4, scope);
        return;
    };
    // 4:  writeln 'Вводь свій варіант:'
    //     input guess
    if (step == 4) {
        CLI.writeln('Вводь свій варіант:');
        CLI.on_input(function(input) {
            scope.guess = input;
            exec(5, scope);
        });
        return;
    };
    // 5:  if guess == number then 9
    if (step == 5) {
        if (scope.guess == scope.number) {
            exec(9, scope)
        } else {
            exec(6, scope)
        };
        return;
    };

    // 6:  if guess < number then 8
    if (step == 6) {
        if (scope.guess < scope.number) {
            exec(8, scope)
        } else {
            exec(7, scope)
        };

        return;
    };

    // 7:  writeln 'Ні, моє число менше, пробуй ще' 
    //     goto 4
    if (step == 7) {
        CLI.writeln('Ні, моє число менше, пробуй ще');
        exec(4, scope)
        return;
    };

    // 8:  writeln 'Ні, моє число більше, пробуй ще'
    //     goto 4
    if (step == 8) {
        CLI.writeln('Ні, моє число більше, пробуй ще');
        exec(4, scope)
        return;
    };

    // 9:  writeln 'Ого, так швидко вгадав. Грати ще раз?')
    //     input answer
    if (step == 9) {
        CLI.writeln('Ого, так швидко вгадав. Грати ще раз?');
        CLI.on_input(function(input) {
            scope.answer = input;
            exec(10, scope);
        });
        return;
    };

    // 10: if anwser == 'так' then 3
    if (step == 10) {
        if (scope.answer == 'так') {
            exec(3, scope)
        } else {
            exec(11, scope)
        };

        return;
    };

    // 11: if answer == 'ні' then 13
    if (step == 11) {
        if (scope.answer == 'ні') {
            exec(13, scope)
        } else {
            exec(12, scope)
        };
        return;
    };

    // 12: writeln 'Ну то так чи ні?'
    //     input answer
    //     goto 10
    if (step == 12) {
        CLI.writeln('Ну то так чи ні?');
        CLI.on_input(function(input) {
            scope.answer = input;
            exec(10, scope);
        });
        return;
    };

    // 13: writeln 'Ну тоді бувай!'
    if (step == 13) {
        CLI.writeln('Ну тоді бувай!');
        CLI.on_input(function() {}); 
        return;
    };
}

exec(1, {});

Можна звісно коротше, якщо робити умовний оператор без переходів в інший блок:

function exec(step, scope) { 
    console.log(step, scope);
    if (step == 1) {
        CLI.writeln('Як тебе звуть?');
        CLI.on_input(function(input) {
            scope.user = input;
            exec(2, scope);
        });
        return;
    }
    if (step == 2) {
        CLI.writeln('Привіт,' + scope.user);
        CLI.writeln('Давай пограємо гру відгадай число?');
        exec(3, scope);
        return;
    }
    if (step == 3) {
        CLI.writeln('Я загадав число від 1 до 10.');
        scope.number = Math.floor(Math.random() * 10) + 1;
        CLI.writeln('Спробуєш вгадати?');
        exec(4, scope);
        return;
    }
    if (step == 4) {
        CLI.writeln('Вводь свій варіант:');
        CLI.on_input(function(input) {
            scope.guess = input;
            exec(5, scope);
        });
        return;
    }
    if (step == 5) {
        if (scope.guess == scope.number) {
            CLI.writeln('Ого, так швидко вгадав. Грати ще раз?')
            CLI.on_input(function(input) {
                scope.answer = input;
                exec(6, scope);
            });
            return;
        }
        CLI.writeln('Ні, моє число ' +
            (scope.guess < scope.number ? 'більше': 'менше')
            + ', пробуй ще');
        exec(4, scope);
        return;
    }
    if (step == 6) {
        if (scope.answer == 'так') {
            exec(3, scope);
            return;
        }
        if (scope.answer == 'ні') {
            CLI.writeln('Ну тоді бувай!');
            CLI.on_input(function(){});
            return;
        }
        CLI.writeln('Ну то так чи ні?');
        CLI.on_input(function(input) {
            scope.answer = input;
            exec(6, scope);
        });
        return;
    }

};

exec(1, {});

Які з цього висновки? Ми можемо уникнути зайвих вкладень якщо нам треба зробити ланцюзок колбеків – запит, обробка відповіді, новий запит що залежить від результатів, обробка відповіді і т.д. Головне, аби це не був ланцюжок з тисяч колбеків, бо нарвемось на переповнення стеку. Тоді доведеться писати власну оптимізацію хвостової рекурсії.


Filed under: Кодерство Tagged: книжки, JavaScript, розробка, Python

vagrant up!

Vagrant – це як висловився dmytrish – CLI до VirtualBox. (А також для інших систем віртуалізації. Перекладається слово як бродяга, і далі ви зрозумієте чому. Для чого там інтерфейс командного рядка? Ну, щоб швидше створювати і перемикатись між віртуальними машинами, а вони сьогодні ой як потрібні.)

Для чого? Ну, для початку – щоб ізолювати середовище розробки. Бо, коли я пишу на своїй машині команду python, мені пропонують наступний вибір:

python                python3.3
python2               python3.3-config
python2.7             python3.3m
python2.7-config      python3.3m-config
python2.7-dbg         python3.4
python2.7-dbg-config  python3.4-config
python2-config        python3.4m
python2-dbg           python3.4m-config
python2-dbg-config    python3-config
python3               python3mu
python3.2             python-config
python3.2-config      python-dbg
python3.2mu           python-dbg-config
python3.2mu-config

Трохи забагато, чи не так? А от якби я пробував кожен наступний python у своїй віртуалці – все було б набагато акуратніше.

Чи наприклад інший приклад. Я хочу тестувати взаємодію бота з медіавікі. Для цього мені бажано її мати (не тестувати ж на живій, а раптом помилку зроблю?), а їй потрібен LAMP-стек, тобто апач або nginx, php, mysql. І це потрібно мені не завжди, а лише поки я ганяю тести до медіавікі. Було б класно тримати для цього окрему віртуальну машину, щоб можна було її вмикати і вимикати при потребі. Отож, давайте розберемось як Vagrant в такому випадку може допомогти.

Інсталятор можна скачати зі сторінки http://www.vagrantup.com/downloads, або встановити за допомогою пакетного менеджера:

sudo apt-get install vagrant

Далі команда

vagrant init hashicorp/precise32

Створює в поточній директорії файл з назвою “Vagrant” що містить конфігурацію для запуску віртуальної машини на основі образу 32-х розрядної Ubuntu 12.04.

vagrant up

Запускає віртуальну машину описану в даному файлі.

vagrant ssh

Входить в запущену віртуальну машину.

За замовчуванням Vagrant робить директорію вашого проекту, доступною віртуальній машині за адресою /vagrant. Тому обережно з rm -rf / бо це потре і ваш проект. А окрім цього можете робити з віртуальною машиною що завгодно. Також зручно мати проект і всередині віртуальної машини і на хості, бо можна одночасно редагувати в зручному редакторі з хоста, і деплоїти з віртуалки.

О, щодо розгортання. Ми можемо зайти по SSH і поставити апач, але тоді це доведеться робити всім хто використовуватиме Vagrant з вашим проектом. Замість цього можна скористатись автоматичним деплойментом.

Інструкція пише що Apache ставиться отаким скриптом:

#!/usr/bin/env bash

apt-get update
apt-get install -y apache2
if ! [ -L /var/www ]; then
  rm -rf /var/www
  ln -fs /vagrant /var/www
fi

А також директорія яку він хостить перенаправляється на директорію з нашим проектом.

Його можна зберегти в директорії проекту як bootstrap.sh і відредагувати файл Vagrant так, щоб він запускав цей файл при старті машини, якщо цього не було зроблено ним раніше. Тепер наш Vagrant-файл повинен виглядати приблизно так:

Vagrant.configure(&quot;2&quot;) do |config|
  config.vm.box = &quot;hashicorp/precise32&quot;
  config.vm.provision :shell, path: &quot;bootstrap.sh&quot;
end

Якщо машина вже була запущена, їй можна скомандувати перезапуститись і виконати файл провізіонування:

vagrant reload --provision

Якщо ви отримуєте помилку:

/home/bunyk/post2peer/Vagrantfile:6: syntax error, unexpected ':', expecting kEND
  config.vm.provision :shell, path: "bootstrap.sh"

То значить у вас стара версія Ruby, оновіться.

Як побачити що Apache працює? Браузером звісно, але для цього треба відкрити на віртуалці порт для HTTP. Додаємо до конфігурації такий рядок:

  config.vm.network :forwarded_port, host: 4567, guest: 80

Він каже зробити порт 80 віртуалки доступним на хості як 4567. Щоб зміни мали ефект – перезавантажте машину:

vagrant reload

localhost:4567 повинен показувати вміст файлу index.html, якщо такий наявний в вашому проекті.

Коли ви закінчили роботу з машиною, можна зробити три речі:

  • vagrant suspend – зберегти стан машини і зупинити її. Плюси – все зберігається як ви й залишили і для старту буде потрібно лише 5-10 секунд. Мінуси – машина займатиме диск, бо потрібно буде записати знімок її пам’яті.
  • vagrant halt – зупинити всі програми і вимкнути машину. Вміст диску зберігається, вміст оперативки – звісно ні. Старт буде трохи довшим, бо доведеться знову ініціалізувати всі процеси…
  • vagrant destroy – знищити всі сліди того що ви взагалі працювали з машиною (диск). vagrant up буде працювати ніби вперше. Плюси – нічого не треба зберігати, економиться диск. Мінуси – при старті багато часу піде на провізіонування.

І ще інша перевага Vagrant в тому, що він може керувати не тільки VirtualBox, а й всякими там VMWare, і навіть AWS. Треба просто вказати йому інший провайдер – і ваш сервер в хмарах. Але про це якось іншим разом.

Ах, і я на початку казав про медіавікі. Ну, вона ставиться ось так:

sudo apt-get install nfs-kernel-server # якщо NFS не було встановлено
git clone https://gerrit.wikimedia.org/r/mediawiki/vagrant
cd vagrant
git submodule update --init --recursive
./setup.sh
vagrant up

Filed under: Інструменти, Кодерство Tagged: віртуалізація, розробка

Розробка керована питаннями

Чому розробка ПЗ це так важко? Тому що воно займає тисячі рядків коду і їх всіх важко набрати? Та ні, якраз навпаки, в дні коли в мене виходить написати сотні рядків коду я втомлююсь менше ніж коли пишу один, чи взагалі не пишу.

Важко звісно не набирати рядки, а ВИРІШИТИ які рядки потрібно набрати. В дні коли я пишу їх багато – це рішення просте, в дні коли мало – рішення часом взагалі неможливо побачити. І втомлюється людина від прийняття рішень, а також особливо від рішень які ще не прийняті. Навіть існує ефект Зейгарнік, який проявляється в тому, що люди схильні швидко забувати вирішені питання і закінчені справи, а незакінчені будуть триматись в мозку і займати його ресурси.

Качечка теж втомилась :)

Качечка теж втомилась :)

Тому, якщо робота застопорилась, можна спробувати застосувати модифікацію методу каченяти, яку я назвав QDD – розробка керована питаннями. Для цього потрібно написати питання над рішенням якого ви зараз думаєте в якийсь текстовий файл чи на листок паперу. І уявляти при цьому що це чат зі всезнаючим експертом, який обов’язково допоможе. Бо так і є, це час з собою майбутнім. Явне формулювання питання дозволяє сфокусувати думки, а часто зрозуміти що це не те питання яке потребує вирішення.

Якщо ще додавати до кожного запису час, і коротко записувати відповіді на запитання, можна отримати журнал який показує на що йде час розробки, і чому код написаний саме так а не інакше (бо виникали такі питання і знаходились такі відповіді). Якщо код буде написано неправильно – можна буде знайти момент в який неправильне рішення до нього привело. Якщо буде поставлена наступна задача – легше буде оцінити скільки питань і якої складності треба буде вирішити для виконання задачі. Бо розбиття роботи на пункти плану і оцінка їх по аналогії з вже виконаними пунктами – єдині відомі способи оцінити необхідний для роботи час. Для оцінки по аналогії покладаються на пам’ять, хоча журнал був би надійнішим.

Такий метод можна застосувати до вирішення інших проблем, що не стосуються коду. Наприклад до вгризання в якусь складну ідею, чи до пошуку рішення якоїсь життєвої проблеми. Багато великих питань можна розбити на менші питання, головне попрактикуватись.

Ну й наостанок дам приклад того про що я пишу – лог питань що виникають при розробці гри в “п’ятнадцять”: bunyk.github.com/blob/master/fifteen/worklog.txt. Сама гра.


Filed under: Кодерство, Психософія Tagged: психологія, розробка

Чим відрізняється декоратор від адаптера? (І про фасад)

Цих вихідних їхав поїздом додому, і в купе побачив на столі книжку “Heads first Design Patterns” видавництва O’reilly. Моїми сусідами по купе були хлопець з дівчиною. Вони виглядали сонними, і навіть плутали напрям поїздки (думали що поїзд до Києва їде, а не до Франківська, хоча їхали до Франківська), і активно проявляли намір лягти спати, тому я запитав:

- Можна я вашу книжку подивлюсь? Бо завжди хотів знати чим відрізняється декоратор від адаптера.

На що хлопець відповів:

- Так, звісно.

А дівчина:

- Ви що, теж програміст?! Як вас всіх скрізь багато розвелось!

Вони лягли на свої полиці, я теж підклав під голову рюкзак, вмостився на своїй полиці і взявся за книжку.

Книжка чудова, всім рекомендую. Правда, напевне дорога як холєра, але якщо вам пощастить до неї дістатись – читайте без жодних сумнівів. Щоб довго не тягнути інтригу, скажу що про відмінність декоратора і адаптера я таки дізнався. Там в цій книжці навіть цілий діалог між декоратором та адаптером описують, де ці два шаблони доводять один одному хто з них крутіший. :)

Отож, і декоратор і адаптер – шаблони обгортки. Вони при створенні екземпляра себе приймають якийсь об’єкт, і запам’ятовують його десь в своїх атрибутах. Ключова відмінність – декоратор має той самий тип що й огортуваний ним об’єкт, тобто інтерфейс об’єкта що декорується не змінюється, просто його функціональність розширюється, а адаптер – змінює інтерфейс зовсім.

Як приклад для декораторів наводили клас – напій.

# coding=utf-8
class Beverage(object):
    def __init__(self,
        description = 'Невідомий напій', 
        price = 0,
    ):
        self._description = description
        self._price = price

    @property
    def description(self):
        return self._description

    @property
    def price(self):
        return self._price

class Tea(Beverage):
    def __init__(self,
        description = 'Чай',
        price = 2,
    ):
        super(Tea, self).__init__(description, price)

class Coffee(Beverage):
    def __init__(self,
        description = 'Кава',
        price = 3,
    ):
        super(Coffee, self).__init__(description, price)

tea = Tea()
print tea.description, tea.price # чай 2
coffee = Coffee()
print coffee.description, coffee.price # кава 3

А тепер уявімо собі що нам треба ще написати класи для тих самих напоїв, але з цукром, а ще з молоком. Можна було б наслідуватись, але тоді в нас буде ціла купа різних класів. Цукор і молоко – це декоратори напою.

class BeverageDecorator(Beverage): # важливо що декорований напій, це теж напій
    _description = 'Невідомий додаток'
    _price = 0
    def __init__(self, beverage):
        self.beverage = beverage
    
    @property
    def price(self):
        return self._price + self.beverage.price

    @property
    def description(self):
        return self.beverage.description + ' і ' + self._description

class Sugar(BeverageDecorator):
    _description = 'Цукор'
    _price = 0.1

class Milk(BeverageDecorator):
    _description = 'Молоко'
    _price = 0.2

my_coffee = Milk(Sugar(Sugar(Coffee())))
print my_coffee.description, my_coffee.price
# Кава і Цукор і Цукор і Молоко 3.4

Бачимо, що декорований напій – все одно напій, хоча й з дещо зміненою функціональністю. Адаптери натомість можуть взяти один об’єкт і повернути цілком інший.

class FahrenheitThermometer(object):
    @staticmethod
    def get_temperature():
        return 451

class CelsiusAdapter(object):
    def __init__(self, fahrenheit_thermometer):
        self.thermometer = fahrenheit_thermometer

    def __call__(self):
        return (self.thermometer.get_temperature() - 32) * 5.0 / 9

f = FahrenheitThermometer()
print f.get_temperature()
c = CelsiusAdapter(f)
print c()

Бачимо що адаптером ми можемо цілком поміняти інтерфейс нашого об’єкта, змусити його повертати інші дані, які запитуються іншим способом.

Багато із звичних декораторів в Python є насправді адаптерами. Тому можна сказати що в Python декоратор – це обгортка, яку крім того можна ще й особливим синтаксисом застосувати до огортуваного об’єкта. Той же @property, @staticmethod – змінюють інтерфейс виклику методу, роблячи його зручнішим. @twisted.internet.defer.inlineCallbacks – теж змінює інтерфейс, перетворюючи співпрограму (coroutine) на набір асинхронних функцій.

Крім того, є особливий вид адаптера, який адаптує зразу кілька об’єктів, тільки називають його фасадом. Наприклад, нехай ми маємо такі об’єкти як вольтметр, та амперметр, і хочемо виміряти потужність нашої лампочки. Для цього треба послідовно під’єднати амперметр, паралельно вольтметр, і помножити їх показники. Це можна абстрагувати ватметром (в якого в реальності все одно чотири клеми, але не зациклюйтесь на реальності, ми тут моделі будуємо):

class Voltmeter(object):
    def value(self):
        return 220

class Ammeter(object):
    def value(self):
        return 0.36

class Wattmeter(object):
    def __init__(self, voltmeter, ammeter):
        self.voltmeter = voltmeter
        self.ammeter = ammeter

    def value(self):
        return self.voltmeter.value() * self.ammeter.value()

w = Wattmeter(Voltmeter(), Ammeter())
print w.value()

Взагалі, це дуже часто застосовні адаптери, і їх не обов’язково знати щоб застосовувати. Але просто іноді варто знати значення цих слів, щоб розуміти про що люди пишуть.


Filed under: Кодерство, Нещоденник Tagged: книжки, розробка, Python

Ітеративно і інкрементно

Я хотів написати великий пост про те як в п’ятницю пробив стіну, об яку бився головою від понеділка і це принесло таке полегшення що навіть той факт що в суботу в мене хтось в маршрутці свиснув гаманця з готівкою, але чомусь не виходить зібратись з думками. Може я все надто ускладнюю?

Тому тут просто процитую те що написано в підвалі блогу Едді Османі:

Спочатку зроби це, потім зроби це правильно, а після цього зроби це краще. Це одна з основ яку я завжи тримаю в голові розробляючи що завгодно.

Якщо ви розробник який хочу вивчити щось нове, не залежно від рівня навиків, пам’ятайте, що спробувати зробити хоч щось так само важливо як і прочитати як це робиться.

Створіть новий gist чи fiddle, відкрийте консоль і експериментуйте. Це, бляха, весело!

І справді, я тут подумав, що програмування перестає приносити задоволення, коли ти власне перестаєш втілювати ідеї і фіксиш баги, фіксиш баги, сидиш на нарадах, фіксиш баги.

Наступний “мінус” пов’язаний з тим, що розробка грандіозних ідей – це задоволення, а пошук паршивих маленьких “жучків” – це всього лише робота. В кожній творчій справі бувають жахливі періоди одноманітної й клопіткої праці, і програмування не є винятком.

Хоча пошук багів звісно це теж трішки творчий процес, гідний Шерлока Холмса, але варто придумати щось аби зробити його ще більш творчим. Прописувати в коді контракти по ходу пошуку?


Filed under: Кодерство, Нещоденник Tagged: психологія, робота, розробка

Журнальне програмування, JavaScript

Я вже якось писав що без написання коментарів можна обійтись, якщо використовувати рефакторинг “extract method”. Це не означає що коментарі не варто писати, це означає що їх потрібно писати на іншому рівні абстракції і бажано в мові предметної області. Як цього навчитись – складне питання. Щоправда недавно вичитав в пості Дмитра Сіренка на Google+ ідею про те що коментарі краще робити виводом в якийсь лог.

Ця ідея для мене нова і мені дуже сподобалась. По-перше це допоможе краще думати про що ж все таки писати в коментарі. Про те що відбувається:

Якби програмки мали Твіттер...

Якби програмки мали Твіттер…

А що ще краще – такі коментарі повинні сильно допомогти в розумінні коду JavaScript, тому що в JavaScript код виконується як реакція на якусь подію, тому передбачити що за чим виконуватиметься – важко. А при таких коментарях – відкрили термінал, і читаємо коментарі в хронологічному порядку виконання програми:

dragExcercise plugin started for Object[div.exercise, div.exercise] exercises.js (рядок 8)
heißen Ordnung exercises.js (рядок 22)
content put into element exercises.js (рядок 28)
incorrect variant exercises.js (рядок 35)
heißen mir exercises.js (рядок 22)
content put into element exercises.js (рядок 28)
incorrect variant exercises.js (рядок 35)
leaves some other element exercises.js (рядок 45)
mir mir exercises.js (рядок 22)
element already occupied exercises.js (рядок 24)

Так працює код вправ для інтерактивного підручника німецької.

Коментарі записуються як параметри функції c яка описується наступним чином:

    //var c = function() {}; // for production
    var c = console.log.bind(console); // for debug

З цим є дві проблеми. Перша – навіть якщо не забути про те що треба переставити комент, на продакшні буде зайвий виклик функції. Друга – функція підсвічується в редакторі як функція, а не як коментар. Другу можна виправити відредагувавши файл з описом синтаксису мови. Це просто. Першу проблему хотілось би виправити макросами в стилі С. З директивами IFDEBUG, та двома режимами білду – debug та release.

Проблеми можна проігнорувати, а можна подумати про препроцесор. Який коментарі виду

    //-> message

перетворюватиме на

    console.log('message');

в дебаг режимі, і прибиратиме їх з реліз версії коду. Але препроцесори вже є, наприклад той же CoffeScript. Тільки от не знаю чи важко на ньому таке реалізувати… Може варто спробувати зразу осідлати ClojureScript?


Filed under: Кодерство, Павутина, Розмітка Tagged: JavaScript, розробка

Це не так просто – розуміти.

Публікація про те як це, коли код доводиться читати а не писати, і що тоді робити. Тобто, як читати код?

Neo: Do you always look at it encoded? Cypher: Well you have to. The image translators work for the construct program. But there's way too much information to decode the Matrix. You get used to it. I...I don't even see the code. All I see is blonde, brunette, red-head. Hey, you uh... want a drink?

Нео: Ви завжди дивитесь не неї зашифрованою?

Сайфер: Ну, ми змушені. Транслятори зображення працюють над програмою конструювання. Але тут надто багато інформації щоб розкодувати Матрицю. Ти звикнеш. Я… Я навіть не бачу код. Все що я бачу – блондинка, брюнетка, руденька. Ей, ти еее… не хочеш випити?

Передмова

На моїй новій роботі (я не впевнений що мені дозволяє розповідати договір про нерозголошення, тому абстрагуватимусь як можу), проект задля якого мене найняли (там має бути Python, який як відомо легко читати і який я знаю досить добре, і проект новий) ще не почався. Але тим часом аби я не нудьгував, мені дали задачу з іншого проекту, там JavaScript, LeafLet (о, він чудовий), але моя задача – не така проста як видавалась спочатку.

Проблема перша – я не знаю ООП в JavaScript. Дякуючи документації до LeafLet, і тому що ООП там реалізовано через нього – проблема майже розв’язана. Ну й звісно JavaScript я в терміновому порядку підтягую.

Проблема друга – проект з галузі Business Intelligence. А я про схему даних “сніжинка”, OLAP-куб та інші подібні речі чую вперше. Але я почитав вікіпедію, подивився всілякі відеопояснення і тепер маю певне поняття. Якось ним поділюсь, може хтось скаже що моє поняття про BI хибне.

Третя, і головна проблема – купа коду без жодної документації. І на відміну від попередньої роботи (prom.ua), де проект пишуть роками і далі будуть, тут проект писали пів року (судячи з логів СКВ), дедлайн вже в кінці місяця, тому рефакторингом мене попросили не займатись. Ще до того як я запитав чи можна. Про документацію я не питав, тому що документація – це нереально, якщо ви звісно не пишете якусь бібліотеку з відкритим кодом. Зовсім нереально. Але юніт тести – в нашому випадку теж дуже важко, через специфіку середовища. Хоча менеджер сказав “Хочеш тести? Дуже добре!”, і при цьому якось дуже підозріло посміхнувся.

Хоча код знадобилось би трішки підчистити, тому що:

nested_code

Тому мій єдиний вихід – ввімкнути щось схоже на “Push it to the limit“, і поринути в читання. Але код – це не книжки (я читав книжку про те як читати книжки, а про те як читати код – не читав), крім того автор книжки зазвичай хоче аби ви прочитали книжку, а автор коду – лише аби комп’ютер той код зміг виконати, та й досвіду читання коду в мене набагато менше, тому думаю треба дізнатись як це роблять інші. Переберу-но я кілька статтей:

Мотивація

Елі Бендерський пише про те як розуміти власний код. Код який неможливо зрозуміти, на його думку – це такий самий, або можливо ще більший гріх, ніж код який не працює. А програмісту який з гордістю заявляє що не розуміє код який писав тиждень тому, треба давати в писок аби вибити з нього ту гордість. За цим посиланням мені “дають в писок”. Хоча я гордився не тим що написав незрозумілий код, а тим що я його таки зрозумів і навіть трішки спростив. :)

Методи написання коду який буде вам зрозумілим – регулярно його перечитувати і переписувати.

Якийсь невідомий чувак пише про те що тупо прокручувати чужі лістинги вирячившись на них з кислою міною – тупо. Потрібно полюбити читати код, і розглядати це не як каторгу – а як можливість. Тому що написання коду – як і література. Ви не навчитесь писати класний код, якщо не будете читати класний код. Ви не навчитесь читати код якщо не будете намагатись його побільше читати. Тому й поганим кодом не варто нехтувати. Хоча б для того аби розуміти який код поганий, а який – ні.

Зібрані поради

  • Зрозуміти що від програми вимагається і для чого її пишуть. Перед тим як робити все інше.
  • Спитати автора.
  • Пройтись за допомогою зневадника.
  • Вставляти багато print та assert.
  • Записувати всі відкриття в письмовій формі.
  • Спробувати щось змінити і подивитись що зміниться, а що зламається.
  • Парне програмування з розумним колегою чи автором. Спробуйте думати вголос. Може виявитись що в вас різні думки і почнеться продуктивна суперечка.
  • Код не читають рядок за рядком. Якщо щось не розумієте – можливо ви ще не прочитали код в кінці який потрібен для того щоб розуміти код посередині. Пропускаємо, ставимо закладку і повертаємось до нього потім.
  • Знайти функцію main, чи як там називається точка входу і читати з неї. Якщо точок входу багато (наприклад багато обробників подій) – виписати їх список.
  • Прочитати документацію викликів зовнішніх бібліотек. (На щастя сторонні бібліотеки документуються частіше.)
  • Починати з простих частин. Якщо ти не можеш розуміти навіть ті частини що здаються простими – код або занадто заплутаний, або ти недостатньо знаєш мову чи фреймворк.
  • Рефакторинг. Наприклад дайте нормальні імена ідентифікаторам, кілька разів застосуйте extractMethod
  • Додавання коментарів туди ж… Тільки будьте певні що вони не введуть когось хто прийде після вас в оману.
  • Якщо поняття не маєте як почати писати коментарі – описуйте для функції трійки Хоара.
  • Почати читати з тестів. Якщо таких нема – покривати тестами. (А тих хто написав код без тестів – покривати матами :D ). Подивитись чи тести проходяться. Якщо не проходяться – значить ви неправильно зрозуміли як повинна працювати програма (і добре що ви це дізнались раніше поки ще є час зрозуміти її правильно), або вона працює неправильно (Співчуваю ви знайшли баг. Якщо ще не впевнені що зможете пофіксити – додайте в багтрекер.)
  • Намалювати граф викликів.
  • Роздрукувати код на кольоровому принтері з підсвіткою синтаксису, розкласти його на підлозі, взяти в руки кілька маркерів та олівців і лазити виділяючи точки входу, важливі виклики, дописуючи коментарі на полях і позначаючи важкодоступні місця.
  • Намагатись зрозуміти лише необхідний мінімум і припускати що решта коду працює так як і повинна. (А як дізнатись як повинна? :) )
  • Згенерувати за допомогою ctags та cscope чи що там у вас є, індекс для прискорення навігації по коду. Хороше IDE – головний інструмент програміста-археолога. Також є інший софт, на зразок LXR (для читання коду Linux), Doxygen, Resharper, купа всякого…
  • Намагатись поміщати елементи в чорний ящик, тобто старатись зрозуміти ЩО вони роблять, а не ЯК вони це роблять. Правда тут є виверт 22 – код містить опис того як щось робиться, а що робиться – нам якраз потрібно зрозуміти. Тим не менш – треба абстрагуватись як тільки зрозуміємо якусь частину і переходити до наступної.
  • Спитати досвідчених користувачів програми, які не обов’язково повинні бути її творцями. Можливо якщо повністю зрозуміти як люди працюють з програмою – не доведеться її читати, можна буде написати свою версію. :)
  • Виділити найважливіші прецеденти, зрозуміти їх.

Посилання

Також варто сказати про те що тут – як з підтягуванням. Знання про те що не варто скрипіти зубами, напружувати прес чи інші м’язи (якщо в вас коліна підтягуються до грудей – це не добре). І не забувати видихати при скороченнях і видихати при розслабленні. І мати стабільний ритм. Але тільки це не допоможе вам підтягуватись 20 разів поки ви не спробуєте 2, потім 3, потім 4 і так далі, регулярно та постійно.

Література


Filed under: Кодерство, Нещоденник Tagged: книжки, робота, розробка

Відкрити браузер і передати йому html

На роботі часто пишу email розсилки. І перед тим як їх тестувати на живому сервері (і на живих клієнта) ми стараємось тестувати їх в локальних умовах. Раніше для цього в конфігу вмикалась опція яка замінювала адресу отримувача на всіх листах що відправляються на адресу розробника чи тестера, після чого десь на порталі натискалась кнопка, чи з інтерактивної сесії Python запускалась функція яка збирала всі дані для заповнення шаблону, рендерила його і посилала листа.

Але це заморочливо. Тепер я вмію мокати smtplib і листи почнуть покриватись тестами. І тоді вже почнеться повне TDD і всім буде добре, бо розробники зможуть виправляти помилки ще до того як відправити їх тестерам.

Тільки от біда, що є речі які автоматичними тестами не покриваються. Наприклад треба переконатись що текст не вилазить за межі кнопочки шаблон якої зверстали наші круті стиляги-верстальщики.

Рішення яке використовую я – під час розробки показати html що генерується у браузері. Як у webtest може об’єкт responce.

Перед тим як щось робити самому я звичайно погуглив, і знайшов спосіб, який щоправда трохи застарілий. Потрібно хоча б заголовок Content-Type передавати, тому що його не задають в мета-тегу, а в заголовках формату MIME.

Власне ось сам код – веб-сервер в одній функції що відповідає на один запит:

import webbrowser
from wsgiref.simple_server import make_server
def show_in_browser(html):
        html = html.encode('utf-8')
        def wsgiapp(environ, start_response):
        headers = [('Content-type', 'text/html; charset=utf-8'),
                   ('Content-Length', str(len(html)))]
        start_response('200 OK', headers)
        return [html]

    # нульовий порт означає "знайди вільний сам"
    server = make_server('127.0.0.1', 0, wsgiapp)
    webbrowser.open('http://127.0.0.1:%s' % server.server_port)
    server.handle_request() # один запит

Мене на рев’ю запитали чому ж я не запишу html в тимчасовий файл? Видно я вже занадто веб-девелопер, бо раніше не знав як таке зробити. Зараз знаю як створити тимчасовий файл, але щось локальні файли відкриваються неадекватно. В текстовому редакторі.


Filed under: Кодерство, Розмітка Tagged: HTML, розробка, Python

Програмна археологія і проблема власника коду

Уявіть собі, виявляється така дисципліна програмної інженерії як археологія існує досить давно, і в 2001 спеціально по ній навіть проводили конференцію.

Ну і я, собі теж, маючи необхідність прикрутив до Vim таку лопату, яка дозволяє копати в Mercurial:

103 command! -nargs=* Blame call Blame(<f-args>)
104 function! Blame()
105     " gets zero, one or two params
106     " first param – revision to annotate (if false – don’t pass)
107     " second param – pattern to search
108     let revision = (a:0 >= 1) && a:1 ? ‘ -r ‘ . a:1 :
109     let pattern = (a:0 >= 2) && a:2 ? ‘| grep ‘. a:2 :
110
111     let command = ":!hg blame -nvud % ". revision . "| cat -n ". pattern ." | less"
112     execute command
113 endfunction

Лопата жахлива бо я на VimScript ніфіга не вмію писати. І взагалі не люблю мови в яких оператор конкатенації рядків – це крапочка. :)

Але вона працює, і дозволяє вияснити хто, коли і під яким приводом (зазвичай посилання в Jira) написав код на який я дивлюсь. Найчастіше якщо трапляється якийсь великий WTF то виявляється що це написав мій CEO ще в 2008, не пояснюючи мотивів, бо тоді й не було кому пояснювати. :) Ну, і його питати немає сенсу, не тому що субординація, чи він зайнятий, а тому, що це було дуже давно, і звісно він не пам’ятає.

Але загалом все нормально. Тільки от з’являється проблемка. Коли я хочу збільшити пов’язаність якогось модуля, доводиться робити переміщення методів. Коли я хочу щоб метод похудав на 100-200 рядочків, доводиться робити витягнення метода. Обидва рефакторинги переміщують рядки коду, і для Mercurial змінюють його власника, бо він бачить лише що я видалив одні рядки, і вставив якісь інші. Те що вони однакові він не бачить.

Git вирішує цю проблему за допомогою евристики по метриках подібності. Що правда трохи страшнувато звучить, бо я не знаю чи можна буде передбачити результат його роботи.

Зібравши один модуль я навіть вгорі копірайти написав, аби в разі чого люди знали що за поясненнями можна іти не тільки до мене.
І от маю дилему – привласнювати код якось невиховано, але якщо наступна моя задача знову полягатиме в перечитуванні 300-рядкового методу, то я сильно пожалію що залишив код в такому стані.

Тепер питання: а вам доводилось займатись археологією? Що за проект? Як враження? :)


Filed under: Кодерство Tagged: hg, розробка, Vim

Легенда про самодокументований код

Якось був код:

        if self.get_package_ids() == [None]: # only free package

Він був дещо незрозумілим, тому прокоментованим. Але потім в когось з’явилась ідея додати

    def only_free_package(self):
        return self.get_package_ids() == [None]

І коментар в нашому рядочку став зайвим:

        if self.only_free_package():

Ось і все. Це коротка легенда з простим сюжетом. Її мораль – методи призначені не тільки для повторного використання. Їх також варто використовувати для ізоляції рівнів абстракції.

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

Але дуже часто я бачив псевдокод нижчого рівня ніж Python. Я читав погані псевдокоди?


Filed under: Кодерство Tagged: паттерни, розробка, Python