9 дек. 2011 г.

советы тем кто подрастает

Хотел называть “крик души”, но потом в конце написания поста, решил изменить название на “советы тем кто подрастает”.

В общем есть у меня знакомый, который около двух месяцев назад, попросил поревьювить их код. Код не замысловатый, молодой проект, так что посмотреть все это дело не заняло много времени... Замечания в основном были по архитектуре, о том как код упростить, ну и как сторонник TDD, естественно был у меня главный вопрос - “а где тесты?”.
Естественно о TDD они слышали. В просторах сети читали, вроде бы даже смотрели видео, якобы даже пробовали использовать, но как это применить на конкретно ихнем примере - тут уже сложнее. У меня ушло около недели (это где-то 3-4 часа моего времени), за которое я внес изменение в их базовые классы и естественно написал базовые тесты, так сказать на примере показать, что есть TDD. В качестве напутствия приложил файлик с книгой “Мартин Р. - Чистый код. Создание, анализ и рефакторинг”.

Результат - сегодня на почте я нашел письмо, с примерным содержанием “переделали, посмотри, мы растем, ждем ответ”, полез смотреть... начал писать ответ в письме, но вместо этого решил написать заметку в блог, что бы и другие не наступали на эти грабли.
Повторяю, прошло около двух месяцев, за это время проект не сдвинулся со своего места... т.е. целая команда, полностью перестала развивать проект, они остановили всю разработку ради того что бы внедрить TDD и переписать код, причем код проекта, который еще не запущен - смысл?
Совет 1: Любые нововведения не должны останавливать процесс разработки продукта, Ваша задача делать продукт, а не код. Как бы нам не хотелось сделать все по уму, этого не получится, любой код должен эволюционировать, старые куски должны переписываться (это не сомненно). Код который Вы сейчас пишите, используя все Ваши знания, через некоторое время, для Вас покажется устаревшим, и возникнет желание переписать. Но тогда у нас будет не разработка и улучшение конкретного продукта, а улучшение кода. А кому кроме программиста этот код нужен? Хорошо написанный код может доставить эстетическое удовольствие только тому программисту, который его написал, т.к. для других он будет либо слишком тривиален, либо слишком сложным. Единственное что волнует заказчика/начальника/клиента - код работает согласно требованию.

Едем дальше. Начал просматривать тесты, беглый осмотр и очередная ошибка, её сотню раз обсуждали, во всех статьях это описано, но почему-то многие эту ошибку допускают.
например (пример утрированный):
package ua.lg.moon;
public class MyClass {
 public static int sum(int a, int b){
  return a+b;
 }
 public static int diff(int a, int b){
  return a-b;
 }
}
package ua.lg.moon;
import static org.junit.Assert.*;
import org.junit.Test;
public class MyClassTest {
 @Test
 public void testSum() {
  assertEquals(4, MyClass.sum(2, 2));
  assertEquals(6, MyClass.sum(4, 2));
 }
 @Test
 public void testDiff() {
  assertEquals(MyClass.sum(2, 2), MyClass.diff(6, 2));
  assertEquals(MyClass.sum(4, 2), MyClass.diff(10, 4));
 }
}
Вопрос на засыпку: что мы получим, когда у нас в методе sum будет ошибка?
Ответ: Мы получим 2 не рабочих теста. Это не проблема, когда у нас один класс и два метода-теста, а возьмем реальную систему, где в сотнях, а иногда даже тысячах классов-тестов есть по более десятка методов. И кто-то допускает ошибку в коде, аналогичному методу sum, который используется для сравнения, или предполагается что он “уже проверен и работает”. Результат у нас куча провалившихся тестов.
Совет 2: Каждый тест должен проверять только свою часть. Он не должен зависеть от тех классов которые проверяются в других тестах и которые могут работать не верно. В коде который я ревьювил, я сделал специально ошибку, в классе который получает игрока (в поле игрока, его номер всегда возвращался 1), я получил 80% не рабочих тестов, и если бы я эту ошибку не допустил специально, то найти что ж в коде пошло не так - это уже проблема. Т.е. если мы проверяем в тесте “игровую доску”, и классу “доски”, что бы тест прошел нам нужно передать двух игроков - их лучше создать с нужными параметрами, а не использовать общее хранилище для получения - т.к. при проверке доски мы будем зависеть от работоспособности хранилища пользователей (не важно виртуальное оно или реально). А при поломке хранилища пользователей - у нас так же не проходит тест по “игровой доске” и все остальные тесты.

Совет 3: Называйте тесты так, что бы из названия сразу было видно - что этот метод проверяет. Когда падает тест test32 - “expected:<...> but was:<...>”, то в любом случае придется “заходить” в тест, хорошо если в нем есть комментарии, намного удобнее писать имя теста так - что бы было понятно что он проверяет. Хотя бы так: testPlayerBoardNotNull - уже большинству будет понятно, что этот тест проверял. А вообще есть много способов, некоторые пишут should, или expect и т.д. - тут уже дело каждого, как Вы будете именовать Ваши тесты, сам факт в том - что бы если тест упал - можно было как можно быстрее понять - что упало.

Совет 4: Прочесть книгу “Совершенный код. С. Макконел”. Не важно на каком языке Вы пишете, к любому языку можно отнести то что там написано. Вроде бы код работает, но с первого раза глядя на функцию, понять что он делает - практически не реально, благо хотя бы многие функции имеют описание и достаточно в IDE на функцию навести мышь - что бы узнать “что она делает”. Перечислять постулаты книги смысла нету, так что по коду это извечная проблема многих команд. Прочесть и понять.

Совет 5: Предполагайте, что ваш “движок” может поменяться. Практически все системы что я видел очень сильно связаны с конкретной базой данных, либо некой внешней библиотекой, пример будет про хранение. Обычно после того как людям рассказываешь базовые вещи по архитектуре, для чего все таки нужны абстрактные классы, интерфейсы, как применить это в TDD, то через время у них получается примерно следующее. Есть некий абстрактный класс, например Players у которого есть методы list, get, add, edit и т.д... а затем у них есть PlayersImplMemory и PlayersImplMySql
Соответственно первый хранит все в хешмапе и используется для тестов, а второй уже в промо. Люди которые вроде бы знают что такое “дублирование кода”, сами же его допускают, в обоих классах у них в методе add идет валидация входных данных. Соответственно если я проверяю в тесте валидацию или получение для Memory модели, это не значит что это будет “справедливо” для MySql модели. Более логично - создать класс который будет иметь в себе валидацию, все что общее, например Players, а использовать уже абстрактный класс или интерфейс для хранения, например PlayerStorage. Storage - принимает “сырые данные” и надеется что они будут “правильными” и сохранять их уже в конкретной ситуации либо в памяти, либо в конкретной БД или в шарде, тут уже можно в любой момент поменять направление.

Ну и на последок, самое главное: ваша программа должна быть как конструктор. Каждый кубик - это класс/интерфейс, и если смотреть “сверху” - все должно сводится к “передал параметры”, “получил результат”... если Вы сначала подумаете о том, какие у Вас будут входные параметры, и какие результаты Вы хотите получить, то реализовать это уже вопрос времени. Любую даже самую казалось бы сложную и громоздкую задачу, можно описать нормально и реализовать. Как пример: мне нужно было в уже существующей системе переделать отчеты. В БД лежат данные, в варианте “до меня” был класс и метод, у которого порядка 5-10 параметров передавалось на вход, на выходе String. Нужно генерировать 3-4 типа отчетов и каждый из них должен быть либо в HTML либо в том же HTML но для печати...
В том классе который существовал, там было порядка 4-5 методов приватных, в среднем класс был на 300-400 строк. Тот кто писал - постарался, повыносил все дубликаты формирования в методы и т.д... но меня не устроило - то что в одном месте были собраны как логика получения - так и логика формирования...
После моей переделки у меня получился 1 абстрактный класс TableDataSource, который умел формировать данные из БД, собрать три листа, для thead, body и tfoot. И соответственно под каждый отчет был написан свой наследник, и что бы не запоминать их имена, я создал еще “фабрику”, где были методы getMonthReport, getYearReport и т.д. Соответственно так же у меня получился 1 абстрактный класс TableRender, который в качестве параметра принимал TableDataSource и возвращал String. И уже два наследника TableRenderHtml, TableRenderPrintHtml. После чего добавление нужного отчета сводилось к созданию нового TableDataSource.

В общем как-то так...

1 окт. 2011 г.

Типа непутевые заметки...

Планировал вести заметки каждый день и выкладывать их регулярно, но как оказалось - не все получается так как хочется. Первую неделю моего отпуска мы были довольно таки далековато от тырнета и такой возможности не было. Купленная карточка укр-телекома там отказывалась работать напрочь, поэтому начал вести "заметки" в Evernote. С Evernote было удобно, до тех пока я не уперся в лимит 60Мб который там отведен, для бесплатного аккаунта. Поэтому последние дни своего отдыха я писал напрямую в G+, ну а сейчас решил все это соединить в один пост и дописать не хватающие дни )

Изначально отпуск планировался из серии - приехать, снять хату, жарить кожу на солнце, жарить печень пивом, где-то недалеко от моря, но по некоторым причинам, вышло немного не так...

6 мая 2011 г.

LVM раздел из 2х флешек

Насмотревшись видео от Яндекса решил по извращаться с LVM. В принципе я до этого читал, даже когда-то приходилось добавлять новый диск в работающую систему, но особо как-то не вникал. В одной из лекций упоминался LVM, заинтересовало... Вообщем ничего лучше не придумал как объединить 2 флешки в один "диск"...

Собственно что имеется:
  • флешка на 1Гб
  • флешка на 2Гб
  • Система убунта

Чего хочется:
один раздел на 3Гб