Если нельзя, но очень хочется, то нужно обязательно и ничего в мире не стоит того, чтобы делать из этого проблему!


Интересна Java? Кликай по ссылке и изучай!
Если тебе полезно что-то из того, чем я делюсь в своем блоге - можешь поделиться своими деньгами со мной.
с пожеланием
столько времени читатели провели на блоге - 
сейчас онлайн - 

вторник, 28 августа 2012 г.

Как научить ребенка?

Сделать доступ к знаниям легким, а потом не мешать ему.

Меня сегодня сутра ткнули носом в эту статью.

Подписываюсь везде.

В школе во мне убили любовь к гуманитарным наукам, сейчас возрождаю это все. Не легко. В то время как по точным наукам я всегда делал на 80% быстрее все, что положено: контрольные всем 4-5 вариантам писал, конспект дома за полчасика готовил на пару уроков вперед, а на уроке троллил учительку указывая на ее ошибки, чтобы знала как это неприятно перед всем классом указывать на ошибки учеников, олимпиады и т.д. Для меня было нормально взять в библиотеке пособие для преподавателей и учить по нему, потому как там интеерснее было, чем в учебнике. Так что эта история тру.

Скажем так, то что написала Автор - это предпринимательство в образовании своих детей. И это в ногу со временем.

Пора готовиться к школе.


понедельник, 27 августа 2012 г.

TDD на PHP используя Zend Studio - выделяем из калькулятора зависимость и мочим ее

Краткое содержание прошлых серий:
- подружили Zend Studio и Zend Server
- подружили Zend Studio и GitHub
- написали калькулятор по TDD

Калькулятор умеет суммировать числа в разных системах счисления от 2-ричной до 17-ричной. Его можно развивать дальше (умножение, деление, работа с отрицательными числами), но это не так интересно и будет выглядить +/- так же как и в прошлой серии. Сейчас же пойдем другим путем - от заказчика пришло требование-нежданчик. На базе существующего калькулятора необходимо реализовать калькулятор, работающий с римскими числами, при этом старая возможность суммировать в разных системах счисления должна оставаться не тронутой.

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

Если присмотреться, то в существубщем калькуляторе уже наблюдается нарушение принципа единой ответственности (SRP или Single Responsibility Principle)

class Calculator {    
    
    private $Base;
    private $Digits = "0123456789ABCDEFG";
    
    public function calculate($expression, $base) {
        $this->Base = $base;

        if ($this->isContainsInvalidNumber($expression)) {
            throw new RuntimeException('Invalid number');
        }
        
        if ($base > strlen($this->Digits) || $base <= 1) {
            throw new RuntimeException('Invalid base');
        }
        
        preg_match_all('/['.$this->Digits.']+/', $expression, $out);
        
        if (count($out[0]) < 2 || substr_count($expression, '+') != 1) {
            throw new RuntimeException('Invalid expression format');
        }        
            
        $sum = $this->hexToInt($out[0][0]) + 
               $this->hexToInt($out[0][1]);    

        return $this->intToHex ($sum);
    }

    private function isContainsInvalidNumber($expression) {
        $is_invalid = false;
        for ($index = 0; $index < strlen($expression); $index++) {
            if ($expression[$index] == '+') {
                continue;
            }
            
            $int = $this->toInt($expression[$index]);
            $is_invalid |= ($int === false) || $int >= $this->Base;                
        }            
        return $is_invalid;        
    }
    
    private function intToHex($int) {
        $result = '';
        $low = $int;
        do {
            $high = $low % $this->Base;
            $low = (int)$low / $this->Base;
            $result = $this->toHex($high).$result;
        } while ($low >= 1);
        
        return $result;
    }

    
    private function toHex($int) {
        return $this->Digits[$int];
    }
    
    private function hexToInt($hex) {        
        $sum = 0;
        for ($index = 0; $index < strlen($hex); $index++) {
            $sum = $this->Base*$sum + $this->toInt(substr($hex, $index, 1));
        }
        return $sum;

    }
    
    private function toInt($hex) {
        if (is_numeric($hex)) return $hex; 
        return strpos($this->Digits, $hex);
    }

}

Сейчас можно углядеть что он работает так - получая на вход два числа, валидирует правильность чисел, после конвертирует их в 10ричную систему, после складывает и конфертирует обратно в исходную систему счисления. конвертирует -> валидирует -> суммирует -> конвертирует. Не знаю как у тебя, но со словом калькулятор у меня больше ассоциируется глагол "суммирует", а вот конвертация и валидация - это что-то несвейственное ему. Кроме того именно конвертация+валидация подвергается обстрелу с новым требованием-нежданчиком от любимого заказчика. А раз уж меняется - инкапсулируй! Это один из ООП-шных советов. Давай проследуем ему.

Благо ТДД позволил нам делать часто рефакторинг и сейчас все очень подготовленно к выносу. Но нельзя писать код, пока не будет написан тест, заставляющий это сделать. Напишем его.

Первым делом я хочу выделить логику конвертации из x-ричной системы счисления в 10-ричную.

[тест]
class ConvertorTest extends PHPUnit_Framework_TestCase {
  
    private $Convertor;
 

    protected function setUp() {
        parent::setUp ();
        $this->Convertor = new Convertor();
    }
 
    protected function tearDown() {
        $this->Convertor = null;
        parent::tearDown ();
    }
 
    public function testShouldConvertHexToInt() { // вот самое интересное
        $actual =  $this->Convertor->decode('1ABC', '16');
        $this->assertEquals(6844, $actual);
    }
  
}
[фикс]
class Convertor {    
    
    private $Base;
    private $Digits = "0123456789ABCDEFG";
    
    public function decode($hex, $base) { // все строки кроме этого метода были перенесены копипастом
        $this->Base = $base;
        
        return $this->hexToInt($hex);
    }
    
    private function hexToInt($hex) {
        $sum = 0;
        for ($index = 0; $index < strlen($hex); $index++) {
            $sum = $this->Base*$sum + $this->toInt(substr($hex, $index, 1));
        }
        return $sum;
    
    }
    
    private function toInt($hex) {
        if (is_numeric($hex)) return $hex;
        return strpos($this->Digits, $hex);
    }
}
[коммит] Начал выносить зависимости по конвертации в отдельный класс
[тест]
    public function testShouldConvertIntToHex() {
        $actual =  $this->Convertor->code('6844', '16');
        $this->assertEquals('1ABC', $actual);
    }
[фикс]
class Convertor {    
    
    private $Base;
    private $Digits = "0123456789ABCDEFG";
    
    public function decode($hex, $base) {
        $this->Base = $base;
        
        return $this->hexToInt($hex);
    }
    
    public function code($hex, $base) { // добавили новый метод
        $this->Base = $base;
    
        return $this->intToHex($hex);
    }
    
    private function intToHex($int) { // это скопитырили из калькулятора
        $result = '';
        $low = $int;
        do {
            $high = $low % $this->Base;
            $low = (int)$low / $this->Base;
            $result = $this->toHex($high).$result;
        } while ($low >= 1);
    
        return $result;
    }
    
    private function toHex($int) {  // это тоже скопитырили из калькулятора
        return $this->Digits[$int];
    }
    
   ... 
}
[коммит] Вынес вторую, операцию кодирование в x-ричную систему Теперь сразу два теста, один положительный а другой отрицательный.
[тест]
    public function testShouldValidateTrueIfValid() {
        $this->assertTrue($this->Convertor->isValid('6844', '16'));
    }
    
    public function testShouldValidateFalseIfInvalid() {
        $this->assertFalse($this->Convertor->isValid('1ABG', '16'));
    }
[фикс]
class Convertor {    
    
    private $Base;
    private $Digits = "0123456789ABCDEFG";
    
    public function decode($hex, $base) {
        $this->Base = $base;
        
        return $this->hexToInt($hex);
    }
    
    public function code($hex, $base) {
        $this->Base = $base;
    
        return $this->intToHex($hex);
    }
    
    public function isValid($hex, $base) { // новый метод
        $this->Base = $base;
    
        return !$this->isContainsInvalidNumber($hex);
    }
    
    ... 
    
    private function isContainsInvalidNumber($expression) { // копитыренный метод
        $is_invalid = false;
        for ($index = 0; $index < strlen($expression); $index++) {
            if ($expression[$index] == '+') {
                continue;
            }
                
            $int = $this->toInt($expression[$index]);
            $is_invalid |= ($int === false) || $int >= $this->Base;
        }
        return $is_invalid;
    }
}

?>
[коммит] Вынес вторую, операцию кодирование в x-ричную систему Я вижу много дублирования, а потому сделаю рефакторинг
class Convertor {    
    
    private $Base;
    private $Digits = "0123456789ABCDEFG";
    
    public function decode($hex, $base) { // тут передается $base
        $this->Base = $base; // а тут устанавливается в поле
        
        return $this->hexToInt($hex);
    }
    
    public function code($hex, $base) { // и тут
        $this->Base = $base; // и тут
    
        return $this->intToHex($hex);
    }
    
    public function isValid($hex, $base) { // и тут
        $this->Base = $base; // и тут
    
        return !$this->isContainsInvalidNumber($hex);
    }
[рефакторинг] избавился от дублирования
class Convertor {    
    
    private $Base;
    private $Digits = "0123456789ABCDEFG";
    
    public function __construct($base) { // появился конструктор принимающий основание
        $this->Base = $base;
    }
    
    public function decode($hex) { // зато основание пропало отсюда
        return $this->hexToInt($hex);
    }
    
    public function code($hex) {
        return $this->intToHex($hex);
    }
    
    public function isValid($hex) {
        return !$this->isContainsInvalidNumber($hex);
    }
Естественно это привело к исправлению тестов.
class ConvertorTest extends PHPUnit_Framework_TestCase {
        
    private $Convertor;
    
    protected function setUp() {
        parent::setUp ();
        $this->Convertor = new Convertor(16); // тут
    
    }
    
    protected function tearDown() {
        $this->Convertor = null;
        parent::tearDown ();
    }
    
    public function testShouldConvertHexToInt() {
        $actual =  $this->Convertor->decode('1ABC'); // тут
        $this->assertEquals(6844, $actual);
    }
    
    public function testShouldConvertIntToHex() {
        $actual =  $this->Convertor->code('6844'); // тут
        $this->assertEquals('1ABC', $actual);
    }
    
    public function testShouldValidateTrueIfValid() {
        $this->assertTrue($this->Convertor->isValid('6844')); // тут
    }
    
    public function testShouldValidateFalseIfInvalid() {
        $this->assertFalse($this->Convertor->isValid('1ABG')); // а тут даже тест немного переделал
    }
    
}
[коммит]
[рефакторинг] Дальше пачка методов ничего не делающих, а только валидирующих
    public function decode($hex) {
        return $this->hexToInt($hex); 
    }
    
    public function code($hex) {
        return $this->intToHex($hex);
    }
    
    public function isValid($hex) {
        return !$this->isContainsInvalidNumber($hex);
    }
Вердикт - убрать, переименовав hexToInt в decode, intToHex в code, isContainsInvalidNumber в isValid с инвертированием return!
class Convertor {    
    
    private $Base;
    private $Digits = "0123456789ABCDEFG";
    
    public function __construct($base) {
        $this->Base = $base;
    }
    
    public function code($int) { // переименовали и сделали public
        $result = '';
        $low = $int;
        do {
            $high = $low % $this->Base;
            $low = (int)$low / $this->Base;
            $result = $this->toHex($high).$result;
        } while ($low >= 1);
    
        return $result;
    }
    
    
    private function toHex($int) {
        return $this->Digits[$int];
    }
    
    public function decode($hex) { // переименовали и сделали public
        $sum = 0;
        for ($index = 0; $index < strlen($hex); $index++) {
            $sum = $this->Base*$sum + $this->toInt(substr($hex, $index, 1));
        }
        return $sum;
    
    }
    
    private function toInt($hex) {
        if (is_numeric($hex)) return $hex;
        return strpos($this->Digits, $hex);
    }
    
    public function isValid($expression) { // переименовали и сделали public
        $is_invalid = false;
        for ($index = 0; $index < strlen($expression); $index++) {
            if ($expression[$index] == '+') {
                continue;
            }
                
            $int = $this->toInt($expression[$index]);
            $is_invalid |= ($int === false) || $int >= $this->Base;
        }
        return !$is_invalid; // инвертировали результат перед возвратом
    }
}
[коммит]
[рефакторинг]Теперь можно подключить акуратно перенесенную реализацию к калькулятору и удалить у него лишние методы.
require_once 'application\models\Convertor.php'; // незабываем импорт

class Calculator {    
    
    // это нам больше тут не понадобится - удаляем
    // private $Base; 
    private $Digits = "0123456789ABCDEFG";
    
    public function calculate($expression, $base) {
        $convertor = new Convertor($base); // создаем новый инстанс конвертера

        if (!$convertor->isValid($expression)) { // валидация (осторожно, инвертируем результат)
            throw new RuntimeException('Invalid number');
        }
        
        if ($base > strlen($this->Digits) || $base <= 1) {
            throw new RuntimeException('Invalid base');
        }
        
        preg_match_all('/['.$this->Digits.']+/', $expression, $out);
        
        if (count($out[0]) < 2 || substr_count($expression, '+') != 1) {
            throw new RuntimeException('Invalid expression format');
        }        
            
        $sum = $convertor->decode($out[0][0]) + // конвертер декодирует, а калькулятор суммирует
               $convertor->decode($out[0][1]);    

        return $convertor->code($sum); // конвертер кодирует обратно
    }

}
[коммит]

Что мы имеем сейчас? Зависимость акуратно шаг за шагом была вынесена в отдельный класс Convertor.

Старые тесты калькулятора тестируют два класса вместе, что не совсем юнит тест. Есть зависимость у калькулятора в виде инстанциирования new Convertor в теле метода calculate. Эту зависимость необходимо разорвать.

Часть тестов из текущего интеграционного (т.к. тестирует два класса в связке) перенести в ConvertorTest, а CalculatorTest переписать на моках.

Успокоения ради можно оставить один интеграционный тест, который проверит два класса в связке.

Но прежде всего хотелось бы сделать метод isValid не знающим про знак суммирования.

Так же стоит еще проверку на base вынести в конструктор Converter - ей там больше место.

После этой операции можно приступать к разработке сумматора римских чисел.

Это мой тест лист на текущий момент. Из него выбираем следующий наиболее простой шаг и вперед!

[рефакторинг] Начну с того, чтобы isValid не знал про суммирование, а валидация основания проходила в конструкторе конвертора.

class Calculator {    
    
//     private $Digits = "0123456789ABCDEFG"; // это лишнее теперь
    
    public function calculate($expression, $base) {
        $convertor = new Convertor($base);

//         if (!$convertor->isValid($expression)) {   // валидацию разместили ниже, после парсинга
//             throw new RuntimeException('Invalid number');
//         }
        
//         if ($base > strlen($this->Digits) || $base <= 1) { // это отнесли в конструктор конвертора
//             throw new RuntimeException('Invalid base');
//         }
        
        preg_match_all('/[0-9A-Z]+/', $expression, $out); // поменяли регекспу - теперь она выкусывает все символьно-цифровое
        
        if (count($out[0]) < 2 || substr_count($expression, '+') != 1) {
            throw new RuntimeException('Invalid expression format');
        }        
        
        if (!$convertor->isValid($out[0][0]) || !$convertor->isValid($out[0][1])) { // добавили новую проверку для операндов
            throw new RuntimeException('Invalid number');
        }
            
        $sum = $convertor->decode($out[0][0]) + 
               $convertor->decode($out[0][1]);    

        return $convertor->code($sum);
    }

}

class Convertor {  // а теперь класс конвертора
    
    private $Base;
    private $Digits = "0123456789ABCDEFG";
    
    public function __construct($base) {
        if ($base > strlen($this->Digits) || $base <= 1) { // добавили проверку
            throw new RuntimeException('Invalid base');
        }
        
        $this->Base = $base;
    }
     
        public function isValid($expression) {
        $is_invalid = false;
        for ($index = 0; $index < strlen($expression); $index++) {
//             if ($expression[$index] == '+') { // это лишнее уже
//                 continue;
//             }
                
            $int = $this->toInt($expression[$index]);
            $is_invalid |= ($int === false) || $int >= $this->Base;
        }
        return !$is_invalid;
    }

    ...
[коммит]
[рефакторинг] Сейчас я бы перенес часть тестов на их законное место. Так как класс калькулятора теперь уже занимается только суммированием, то часто тестов из CalculatorTest должна быть перенесена в ConvertorTest. Данные для тестов конвертора будем брать из рассчета того, что передается в него сейчас при запуске тестов CalculatorTest.

class ConvertorTest extends PHPUnit_Framework_TestCase {
        
    public static function validDataProvider() {
        return array(
                array(10, '2', '2'),
                array(10, '4', '4'),
                array(10, '3', '3'),
                array(10, '7', '7'),
                array(10, '11', '11'),
                array(10, '22', '22'),
                array(10, '33', '33'),
                
                array(16, '1', '1'),
                array(16, '2', '2'),
                array(16, '5', '5'),
                array(16, '6', '6'),
                array(16, '8', '8'),
                array(16, '9', '9'),
                array(16, 'A', '10'),
                array(16, 'B', '11'),
                array(16, 'C', '12'),
                array(16, 'D', '13'),
                array(16, 'E', '14'),
                array(16, 'F', '15'),
                array(16, '11', '17'),
                array(16, '19', '25'),
                array(16, '1B', '27'),
                array(16, '1E', '30'),
                array(16, '1C', '28'),
                array(16, '1D', '29'),
                array(16, '22', '34'),
                array(16, '44', '68'),
                array(16, '99', '153'),
                array(16, '100', '256'),
                array(16, '101', '257'),
                array(16, '132', '306'),
                
                array(17, '2', '2'),
                array(17, '3', '3'),
                array(17, '4', '4'),
                array(17, 'D', '13'),
                array(17, 'F', '15'),
                array(17, 'G', '16'),
                array(17, '10', '17'),
                array(17, '1G', '33'),
                array(17, '1F', '32'),
                array(17, '3F', '66'),
                
                array(2, '0101010101', '341'),
                array(2, '1010101010', '682'),
                array(2, '1111111111', '1023'),
        );
    }
    
    public static function invalidDataProvider() {
        return array(
            array(16, 'G'),
            array(2, '10300'),
            array(4, '1A1'),
            array(16, 'QWE'),
            array(16, 'ASD'),    
        );
    }
    
    /**
     * @dataProvider validDataProvider
     */
    public function testShouldConvertHexToInt($base, $hex, $int) {
        $convertor = new Convertor($base);
        $actual =  $convertor->decode($hex);
        $this->assertEquals($int, $actual);
    }
    
    /**
     * @dataProvider validDataProvider
     */
    public function testShouldConvertIntToHex($base, $hex, $int) {
        $convertor = new Convertor($base);
        $actual =  $convertor->code($int);
        $this->assertEquals($hex, $actual);
    }
    
    /**
     * @dataProvider validDataProvider
     */
    public function testShouldValidateTrueIfValid($base, $hex) {
        $this->validate($base, $hex, true);
    }
    
    /**
     * @dataProvider invalidDataProvider
     */
    public function testShouldValidateFalseIfInvalid($base, $hex) {
        $this->validate($base, $hex, false);
    }
    
    private function validate($base, $hex, $isValid) {
        $convertor = new Convertor($base);
        $actual =  $convertor->isValid($hex);
        $this->assertEquals($isValid, $actual);
    }
    
    /**
     * @expectedException InvalidArgumentException
     */
    public function testShouldExceptionWhenBaseIsMoreThan17() {
        new Convertor(18);
    }
    
    /**
     * @expectedException InvalidArgumentException
     */
    public function testShouldExceptionWhenBaseIsLessThan2() {
        new Convertor(1);
    }
}
а вот что остается от стартого CalculatorTest
class CalculatorUnitTest extends PHPUnit_Framework_TestCase {
        
    private $Calculator;
    
    protected function setUp() {
        parent::setUp ();
        $this->Calculator = new Calculator();
    
    }
    
    protected function tearDown() {
        $this->Calculator = null;
        parent::tearDown ();
    }
    
    public function testShouldSumWhenXPlusY() {
        $this->assertEquals(444, $this->Calculator->calculate('123+321', 10));
    }
    
    /**
     * @expectedException InvalidArgumentException
     */
    public function testShouldExceptionWhenInvalidExpression() {
        $this->Calculator->calculate('1', '10');
    }

    /**
     * @expectedException InvalidArgumentException
     */
    public function testShouldExceptionWhenMoreThanOnePlus() {
        $this->Calculator->calculate('1++3', '10');
    }
        
}
[коммит]
 
Я его назвал CalculatorUnitTest, CalculatorTest оставил как есть (для сравнения, на реально проекте я бы удалил интеграционный). Мне в юнит тест для калькулятора предстоит еще добавить несколько тестов, чтобы покрытие калькулятора было максимальным. Тесты не на состояние а на поведение - мне интересно как общается калькулятор с конвертером, и тут без моков нам не обойтись....

Давай сделаем шажок немножечко побольше (рискнем) и провернем сразу несколько операций. Чтобы продемонстрировать что изменилось, покажу diff с помощью тортилки

1) выделим из Calculator зависимость от Converter (будем ее через конструктор инджектить). А так как $base - свойство Converter то из метода calculate так же убираем $base. 


Это приведет к тому, что нам надо будет исправить интеграционный тест (Calculator + Convertor). Напомню его я держу для сравнения, в реальном мире я бы его удалил где-то сейчас (или чуть раньше).


Это безобразно - снова купа дублирования, потому как сетап уже нам не поможет ($base у разных тестов разный и теперь он задается в конструкторе). Но еще можно заметить, что в некоторых тестах мы явно тестируем Convetror и не место тут этим тестам - в общем как ни крути интеграционный тест чем дальше тем больше путает...


2) А что касается юнит теста, то тут все более менее красиво. Настроили в сетапе мок, 

 Сконфигурировали в тесте моки с помощью методов should...... (я повуделял их для лучшей читабельности теста). И усе!
[коммит] 





Пару линков:
Хорошая статья по мокам.
И репозиторий, если удруг понадобится.


Дальше сделаем сумматор римских чисел. Тут чисстое ТДД, нас на секундочку больше не интересует калькулятор - мы просто переименуем Convertor на HexConvertor, и начнем разрабатывать новый класс RymConvertor. Но это уже в следующий раз.... 

вторник, 21 августа 2012 г.

Когда стоит начинать ТДДить?

Поработав с юнит тестами и ребятами, которые учились этому интересному делу я заметил несколько ступенек развития. Перепрыгивать ступеньки не стоит - ничего из этого хорошего не получится. Особенно актуально эта информация для менеджеров, которые хотят на своих проектах видеть TDD - сразу вы его не увидите, как ни крути. Все должно быть естественно и последовательно.

1-я ступень. Тесты не писались и не пишутся. Код разрабатывается по классике: кодим, тестим, дебажим, снова тестим - все ручками. Лечение: начать писать хоть какие-то тесты, в любое время - как удобно, тем самым часть мануальной работы переложить на компьютер. Качество тестов не так интересует, как количество. 

2-я ступень. Тесты пишутся после того, как написан код. Возможно этого кто-то требует сверху, возможно появилась заинтересованность в автотестах. Тесты обычно интергационные, потому как протестировать сильно связанную систему оказыватся не так просто. Лечение: пытаться тестировать не в конце итерации (1-2 недели), а хотя-бы в конце дня, постепенно сокращая по времени итерацию Код-Тест.

3-я ступень. Тесты пишутся параллельно с кодом, но с небольшой задержкой. Обычно они более-менее юнит, а архитектура тестируемого кода, соответственно, более-менее модульная. Дебагом пользуемся, но не зависая надолго. Обычно уже тут специалист test infected и слабо представляет разработку без тестов. Лечение: пробовать TDD.

4-я ступень. Разработка управляемая тестированием, то есть TDD. Первые шаги, основа есть. Все немного максималистично - coverage 100%, никакого debug, только TDD всегда и везде! Лечение: пообщаться (поработать в паре) с такими же TDD практиками, пробовать всевозможные unit testing фреймворки, BDD, помагать любопытным коллегам с вопросами о TDD .

5-я ступень. TDD ретранслятор. Либо XP коуч в проекте, либо тренер-консультант, либо просто на конференциях выступает (или блог интересный ведет). Обычно с TDD все уже достаточно хорошо, как в новых проектах так и legacy. Инструмент используется эффективно в комбинации с другими инструментами или не используется. Лечение: Неизвестно.

6-я ступень. Ваши варианты?

понедельник, 20 августа 2012 г.

Книги, которые хорошо бы прочитать разработчику...

pic  
Описывает test driven development процесс разработки из первых уст.


   
Про юнит тестирование, очень много полезных приемов и решений. Книга-справочник паттернов. 


pic
  
Читать первых 100 страниц, а остальной частью книги пользоваться как справочником. Стоит знать запахи и основные методы борьбы с ними. С этой книги стоит начинать изучать ООП. 



 
Рассказывает как хачить в легаси проектах, чтобы хоть как-то начать рефакторинг/тестирование. Поможет очень в сильно безнадежных проектах. Выход есть! :)

 
pic 
 
Рассказывает про OOP/SOLID принципы и шаблоны проектирования. Читать 1 раз и навсегда запомнить все те основные шаблоны, которые иначе очень сложно просто заучить.


Быстрая разработка программ: принципы, примеры, практика


"Быстрая разработка программ:
принципы, примеры, практика"
Роберт Мартин

 Так же про SOLID, но немного более тяжелым языком.
  


"Чистый код. Создание, анализ и рефакторинг"
Роберт Мартин
Код ревьюшки собранные в одном месте. Описано почему не стоит писать так (пример), а стоит писать так (пример). 




Библия программиста. Буквально библия. Стоит иметь на полке и открывать в рендомных местах, читать и просветляться. Пробовать, переживать, снова открывать в любом месте и снова просветляться. 





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


Проекты делают не роботы а люди. Люди - существа сложные. Книга о том, как принимать это во внимание.

Маршрут Ильичевск-Одесса-Ильичевск

Вот включаю http://nekto.me/radio, там играет фортепиано. Приятная грустная музычка. Ее кто-то написал. Он что-то чувствовал. Его быть может уже и нету с нами, но есть чувство передается.

Сейчас все вокруг спят. Еще пол часа и проснутся. А так как сегодня понедельник, то все очень быстро побежит своим чередом. И хоть я и на море. Но тут все как везде. Я умудрился найти работу в отпуске. Вернее я взял отпуск, чтобы поработать не в Киеве.

Каждый день езжу из Ильичевска в Одессу, чтобы помочь ребятам освоить ТДД на своем проекте. И хоть езжу сам, но на месте встречает напарник Серега, который решил остаться в Одессе. А я решил в Ильичевск с семьей - тут воздух/море чище. И как оказалось 2 часа на дорогу туда-сюда - это мое время. Да, хоть и в маршрутке неудобно, но так я вынужден замедляться. Час туда, час назад. Вот моя маршруточка.


А на море был всего пару раз за прошлую неделю. Два с семьей, раз сам и раз с другом. Забыл свои ласты и маску в Киеве - а просто так купаться не интересно, потому я фоткал, строил замки из песка или собирал ракушки. Купить бы, да пока не повстречался магазин по пути (слабо, значит, хочу).



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

А в центре Одессы все пахнет машинами, так же как в центре Киева. Я даже чихать начинаю. Зачем так много машин? Ладно, надеюсь нефть закончится и я это увижу не мутировав. Но есть в Одессе удивительные места. Много не гулял, в основном по центру.


Вот вчера, к примеру, по аллее здоровья катались с Серегой туда и обратно. Запомнился вопрос Сереги "почему в Киеве такого нет?".



Ладно, пойду собираться. Сегодня рабочий день. Ребята ждут очередной порции ТДД.  Кстати, вот как проходили первые дни нашего тренинга по TDD.

Еще две недели в переди! Продолжение обязательно следует..

В чем разница: "тесты до" или "после" кода?

Как-нибудь потом (или никогда) расскажу, как проходил тренинг с точки зрения тренерской. Сейчас же хочу об TDD. мысли накопились, хочу слить их.

Сам по себе плохо работает, его поддерживают тяжеловесные инж. штуки: рефкторинг, test list, OOP, unit testing, и только если все в комплексе, тогда получится. А еще голову надо сломать об стену, пока не станешь на рельсы - причем ломать надо конкретно, по очень привычным направлениям: не дебажь, но откати; не думай об архитектуре сильно - рефакторинг сам позаботится о ней; пиши код по чуть-чуть, после безжалостно рефакторь его; не реализуй мысль, не обождавшую в тестлисте; не верь тому, что пишешь; страшно - пиши тесты; не предполагай - проверяй тестами. И это только из числа первых шишек.

Понятно, почему в XP коуч в команде личный есть. Обычно, с ТДД потому и спрыгивают, что внедряют его ребята не совсем опытные, в проекте не совсем подготовленном. И тут двухдневным тренингом не отделаться - необходимы комплексные меры, либо очень подготовленный (рефакторинг, ООП, юнит тестирование) проект. А еще, что ставит почти финальную точку - то, что учим ТДД с нуля на простых вещах, а на проекте тем временем ждет хороший такой легаси проект, к которому прикрутить ТДД еще надо ой как постараться - тут тебе и high coupling и low cohesion. Тут-то и возникает первое заблуждение - TDD для проектов "с нуля". Пускай так, но когда наступает этот первый проект - его тесты будут ужасными, потому как все ждали нового проекта, чтобы применить в нем ТДД, но все это время ни на йоту не прокачались в unit тестировании.

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

Очень сильно запомнился вопрос "нафига ТДД", но его можно нагуглить, а я хочу немного приоткрыть вопрос "в чем разница - писать тесты до или после кода?". А разница огромна и вот почему:

- Если тесты писать до, тогда система получится какая-то более модульная, что-ли. Потому как моно сказать (один раз), что код пишется немножечко для тестов. А какой код легче протестить? Оформленный в отдельный модуль. Вот тебе и слабая связность. А если писать тесты после - тогда и вопрос законный "а как тестировать приватные методы?", "как сделать чтобы тесты небыли такими хрупкими" - выделяйте зависимости и пишите юнит тесты, но ломает, потому как дополнительная работа, а код уже написан. Вот и получается что...

- Сode coverage тестами (написанными после кода) низок. А еще полагаться на тулзу, которая говорит что на проекте 80% code coverage можно, но я бы не стал сильно. Потому как можно написать такой один тест, который походит по основным флоу и соберет X%, но это вовсе не значит что функциональные требования покрыты - просто процессор побывал в некотором числе строк. Вот если закомментировать любую строку в коде проекте и при этом соответствующий функциональности тест свалится всегда - вот тут 100% покрытие. С ТДД кода пишется настолько мало, насколько это нужно для зеленой полосы. Но если какая-то строчка проскочила тут, то на этапе рефакторинга она должна быть удалена, потому как удаляется весь код, который не приводит к поломке тестов. Дабл чек. Покрытие максимальное и становится меньше 100% только за счет того, что где-то написали больше чем надо было для теста, а на рефакторинге схалтурили :) Но это тут каждый сам себе доктор. Идем дальше, о рефакторинге...

- Рефакторинг всегда по чуть чуть приводит к той самой хорошей архитектуре, о которой изначально не стоило думать. Умеешь делать рефакторинг, понимаешь принципы ООП - будет тебе счастье. Тут ТДД тем лишь ценный, что предоставляет для рефакторинга зеленую полосу так часто, как это возможно - каждые 10-15 минут (при условии, что с ТДД у нас проблем нет). И рефактори себе на здоровье: поломал - откати, есть зеленая полоса и стало чище - коммить! Когда пишем код а потом покрываем его тестами - рефакторинг не очень то и хочется делать - поломаешь ведь. А с ТДД рефакторинг в кайф. Представь, что тебе придётся собирать кубик рубика (что само по себе задача не простая, даже если знать как) без того самого основания на котором все держится - это возможно, но чертовски сложно, постоянно все ломается. Так и рефакторинг без тестов. Тесты и есть то самое основание кода. Верти себе грани на здоровье. Рефакторинг в кайф - это еще одно преимущество, которое мне дает ТДД в процессе разработки.

- Любой человек ошибается часто. Глупые ошибки он делает приблизительно раз в 5 минут. Это я по себе и коллегам, с кем удалось в паре поработать, сужу. Банальная очепятка. Думаю одно - пишу другое. Нет тестов, нет фидбека о том, что ты ошибся. Поработал 2 часа? Вот тебе и 20 ошибок, которые надобно отладить. А для этого кто нужен? Дебаггер. Ничего против не имею, использую регулярно, но с ТДД я его использую, когда мне хочется (допустим, любопытно что-то), а не когда я вынужден это делать потому как система не работает и я понятия не имею в чем дело - а это самый дорогой процесс. С ТДД его можно максимально сократить. Поломал что-то? 5 минут всего прошло от коммита? Ривертни и попробуй еще раз. Снова ошибся - сделай перерыв.

- Система развивается постепенно, уверенно и предсказуемо. Тут нет места сложным неопределенностям. Поработав два часа я могу с большой уверенностью ответить, когда точно закончу потому, как на руках есть test list в котором описано какие тесты еще остались и спустя 2 часа работы он максимально подробный (и обычно идет на убыль для небольшой фичи). Предсказуемо, значит без стресса. Нет стресса - меньше глупых ошибок, здоровее организм. Но если хочется больше экшена, неопределённости, нравится двухчасовой дебаг с восклицанием "я нашел ее!" (а ошибка-то была банальной очепяткой, которую ТДД просто не допустил бы), тогда лучше по классике.

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

среда, 15 августа 2012 г.

четверг, 2 августа 2012 г.

Где найти персонального тренера по java?

Если тебя интересует практический тренинг по javа есть варианты. Один из них - найти себе тренера с большим опытом работы и хорошими коммуникативными навыками и работать с ним в паре. Есть ребята, которые любят обмениваться опытом и не прочь заработать. Есть ребята, которые делают это просто так - это уж как договоритесь.

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

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

Обычно тренер уделяет учащемуся 1 час в день, если учащийся тренингу уделяет 8 часов в день. Уделять тренингу менее чем 2 дня в неделю неэффективно. По времени обычно на Java Basic курс уходит до 30 часов review тренера. На Java Enterprise - до 60 часов review тренера. Сам же тренинг может растянуться на долго, если не уделять ему каждый день максимум времени - 8+ часов. Ведь, если работать над тренингом full-time (8 часов в день), то Java Basic обычно отнимает 1,5 месяца, а Java Enterprise - 2 месяца. Но если времени уделяешь тренингу только 4 часа в день, то при сохранении стоимости тренинга (объем работ для тренера по review обычно не меняется) увеличивается только время релиза - приблизительно в тех же два раза. Повторюсь, уделять тренингу меньше 2х часов в день или меньше дня в неделю крайне неэффективно - велик шанс, что такой тренинг никогда не закончится.

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

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

1 час консультации тренера обычно стоит 15-60$. Может показаться "ого! как дорого", но на самом деле так как лекции никто тебе читать не будет (если конечно не захочешь сам), а будут только направлять тебя (код ревью и рекомендации куда двигаться), то один ответ может занять всего 15-20 минут времени тренера. За неделю ты можешь получить 4 таких ответов + 1-2 code review, при этом израсходовав всего 2-3 часа работы тренера. А если тренинг продлится 2 месяца - в сумме набежит 16-24 часов. Впрочем, все зависит от тебя - поскольку отчет по времязатратам тренера ты будешь получать с каждым его ответом, то и скоррктировать сможешь свой тренинг.

Если тебя заинтересовало такое предложение - пиши в личку и я помогу найти тебе тренера.