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

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

Цих вихідних їхав поїздом додому, і в купе побачив на столі книжку “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

Пітер Норвіг подарував мені Схему :)

Для тих хто не знає, Пітер Норвіг це не Миколай, а director of research (керівник дослідженнями) в Google. А також автор грубезної книжки зі штучного інтелекту, і класичної статті Як вивчити програмування за 10 років?.

Але якось випадково блукаючи інтернетом я набрів на статтю: (How to Write a (Lisp) Interpreter (in Python))

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

(define (square x) (* x x))

А в Норвіга define обчислюється так:

elif x[0] == 'define':         # (define var exp)
        (_, var, exp) = x
        env[var] = eval(exp, env)

І якщо var – список, то воно як ключ в env не лізе. Вирішив замінювати

(define (square x) (* x x))

на

(define square (lambda (x) (* x x)))

І переписав так:

        elif x[0] == 'define':         # (define var exp)
            (_, var, exp) = x
            if isa(var, str):
                env[var] = eval(exp, env)
            elif isa(var, list):
                env[var[0]] = eval(['lambda', var[1:], exp])
            else:
                RuntimeError('Cannot assign to constant!')

А потім ще додав до стандартних функцій print, input, а потім getattr, та import, за допомогою яких можна доступитись до всієї стандартної бібліотеки Python.

Потім я за допомогою функцій (ага!) і рекурсії описав цикли while та for і намалював дві пентаграми черепахою:

Але писати цикли функціями коли це повинні бути спеціальні форми (як і cond, та напевне define) – це трохи збоченння. Я почав гуглити про то як реалізовують макроси, і о яке щастя, я знайшов ще другу частину: (An ((Even Better) Lisp) Interpreter (in Python))

А потім може я на цій реалізації пройду SICP і напишу компілятор в байт-код Python. І зроблю про це презентацію до наступного performance appraisal…


Filed under: Кодерство Tagged: книжки, lisp, Python

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

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

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: книжки, робота, розробка

Дівчина-салат

Дівчина-салат – це робоча назва прототипу підручника німецької який я на тих вихідних створив, але все не мав нагоди про це написати.

Власне якби я був голлівудським продюсером, я б вже давно на додачу до людини-павука, людини-кажана, жінки-кішки і решти людей-гібридів зняв би “дівчину-салат”. Це історія про дівчинку яка народилась від того що її мама з’їла генетично-модифіковану рослину (а які ви думаєте ще можуть рости на городі у відьми?) з виду Valerianella locusta, також відому як Рапунцель. А вони зняли якусь “Заплутану історію”, яку я не дивився, зате, прочитав оригінал братів Грімм. Казка містить глибоку мораль, суть якої в тому, що коли дітей недостатньо виховали, то навіть якщо їх замкнути в вежі, це не допоможе.

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

Рапунцель з нотатками

Рапунцель з нотатками

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

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

Ще я подумав що подібним чином можна адаптувати звичайні твори. І саме для цієї мети мають чудово підходити зноски які вміє робити LaTeX. Їх було так багато, що довелось застосувати деякі нестандартні модулі аби спростити нумерацію. А ще довелось застосувати XeTeX, він знає про Unicode-шрифти. Бо в звичайному TeX Live отримати одночасно німецькі умляути й кирилицю дуже важко.

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

Коментарі щодо мого розуміння німецької граматики (і української), підказки щодо вдосконалення оформлення видання (ось код для XeTeX, рев’ю можна робити як тут, так і прямо на Google Code) та інша критика вітається.


Filed under: Всяке, Конспекти, Розмітка Tagged: deutsch, книжки, освіта