воскресенье, 11 ноября 2012 г.

How to set EMACS fonts rendering the same as the other KDE applications

Here's how to fix the default too wide and too "round" fonts rendered in EMACS under KDE (and maybe other DE, too).

Write this into the ~/.Xdefault file:

Xft.antialias: 1
Xft.hinting: 1
Xft.hintstyle: hintfull
Xft.rgba: rgb

After that, run the following:

$ xrdb -merge ~/.Xdefaults

And after that EMACS will really get the same-looking fonts as the other system around it. Thank you Atragor for your answer at archlinux.org

Über prompt string for modern commandline

Okay. Today's XXI century already. You have colored commandlines everywhere and you have basically single-user *nix OSes. Now it's time to shift from default commandline prompt strings to something more useful (and fancy, of course).

This is how my prompt looks like now:

This is four-line prompt.

  1. Empty line as a separator.
  2. Clean cut with command history number and 72 dashes.
  3. User and host names, current time, jobs count and current directory.
  4. Emoticon showing the result of last command and the traditional $ symbol (which bears no meaning here, really). If we currently are in the Git repo, then between this tokens the current branch name is being shown in square brackets.

Emoticon at the last line behaves like this:

Big thanks to Make Tech Easier for inspiration.

Here how it was done...

среда, 31 октября 2012 г.

On Clean Coders episodes 6 through 8

Just watched the episodes 6, 7 and 8 of Clean Coders codecasts, namely, "TDD part 2", "Architecture" and "SOLID principles", and just could not stay silent about the awesomeness of this works.

First of all, "TDD part 2" has almost half an hour of peeping over the shoulder of the Uncle Bob while he does the Bowling Kata (yeah, I know that Bowling Kata is boring to death this days, because it's just like everywhere, but nevertheless) and this is just awesome: you can not only watch the proper TDD in action, but the workflow with modern auto-refactoring IDE (IntelliJ IDEA was used there). Don't know what's more inspiring to  hardcore Viperized EMACS user like me.

Second, "Architecture" has the greatest and cleanest explanation of clean functional separation between different parts of modern-style application built with OO principles in mind. I even redrawn it on paper to learn it by heart, but, I suppose, I cannot reproduce it because of legal issues (judging by the licences for codecasts, Uncle Bob is pretty strict about legal issues).

And third, "SOLID principles" expands "Architecture" even more and adds a lot of details to the methods of designing programs cleanly. If you (like me) never worked in serious IT consulting before (say, you're a fresh graduate from some university), you'll be able to get many, many good advices for building/maintaining your next application.

I really think that this three episodes are really core to the whole series and if you cannot afford all of 14 episodes, then go buy "Clean Code" and then only this three episodes. You just don't understand what you miss, honestly.

As for me, I'm ashamed of my early projects now and want to rewrite them all from scratch badly. :(

суббота, 15 сентября 2012 г.

Building changelog from Git log output

That's how you get the name of last tag applied to current branch in Git repo:

git describe | grep -Eo '^[^-]+'

We need to meddle with grep because git describe gives us additional info in the form of


Note the funny literal 'g' before the LAST_COMMIT_HASH.

And that's how you get the list of changes since some COMMIT till the current state of the working copy, in really pretty format "ISO Date (Author) Commit text":

git log --no-merges --pretty=format:'%ai: (%an) %s' COMMIT..HEAD

HEAD is literal "HEAD" there. You can substitute COMMIT token with either commit hash or tag.

Now you write the following script and place it in the root of the codebase of your project:


# Get the list of changes made since the last tag

LAST_TAG=`git describe | grep -Eo '^[^-]+'`
git log --no-merges --pretty=format:'%ai: (%an) %s' $LAST_TAG..HEAD

Name it as changelog and then you can do just:


And get something like this:

2012-09-14 23:50:43 +0400: (E. T.) Some stuff for ticket 1584. re #1584
2012-09-14 23:45:05 +0400: (A. Y.) Some stuff for ticket 1584. test #1584
2012-09-14 15:44:49 -0400: (A. Y.) Refactored some old stuff
2012-09-14 22:24:04 +0300: (D. M.) Improved tests

And this will be changes only since last tag applied. Excellent for quick reports about current upstream.

понедельник, 10 сентября 2012 г.

Essential string processing functions in Common Lisp

It should be noted that true Common Lisp somewhat lacks in several important parts of string-processing, and it shows sometime. Today I needed to heavily process large body of regular text and will write here some functions which are AFAIK considered "standard" in modern languages and which not so easily accessible and/or amazingly intuitive in CL.

In all following code snippets token input stands for input string.

  1. Trimming string from spaces, tabs and newlines

    (string-trim '(#\Space #\Newline #\Return #\Linefeed #\Tab) input))

    All named characters are listed in Hyperspec, 13.1.7 Character Names.

  2. Replacing by regular expressions

    Provided by CL-PPCRE package.

    In next snippet I remove all tokens enclosed in square brackets from the input string:

    (ql:quickload :cl-ppcre)
    (cl-ppcre:regex-replace-all "\\[[^]]+\\]" input "")

    Honestly, I don't know when you can need simple regex-replace and not regex-replace-all. Also, note the double-escaping of special symbols (\\[ instead of \[).

  3. Splitting string by separator symbol

    Provided by CL-UTILITIES package.

    In next snippet I split the input string by commas:

    (ql:quickload :cl-utilities)
    (cl-utilities:split-sequence #\, input)
  4. Making same modification on every string in given list

    In next snippet I trim spaces around all strings in list input-list:

    (map 'list 
         (lambda (input) (string-trim " " input)) 

    However, way better is to wrap the transformation for the string in separate function and call the mapping referencing just the name of transformation:

    (defun trim-spaces (input)
      "Remove trailing and leading spaces from input string"
      (string-trim '(#\Space) input))
    (map 'list #'trim-spaces input)

    Do not forget that string is just a sequence of a characters, and all sequence-operating functions can work on strings in either "abcd" form or '(#\a #\b #\c #\d) form. This applies only to sequence-operating functions, however.

  5. Removing the characters from string by condition

    In the next snippet I leave only the alphanumeric characters in the input string:

    (remove-if-not #'alphanumericp input)

    There are remove-if also.

    As with map, you can make arbitrary complex predicates either with lambdas or wrapping them in separate functions.

воскресенье, 9 сентября 2012 г.

Как написать пятиминутный доклад на студенческую конференцию

Во время учёбы в универе мне регулярно приходилось ездить на различные конференции и делать там доклады. Каждый раз написание короткого содержательного доклада было настоящим испытанием, потому что нужно было рассказать о своём проекте в течение 5 минут, причём за это время нужно было вроде как рассказать о нём всё.

Мучился я почти до самого до пятого курса, когда перед одной из конференций меня не осенило и я не составил для себя короткий простой план текста доклада на проект любого типа. С этим планом доклад обычно получался даже короче, чем на 5 минут. Он успешно опробован на четырёх конференциях, так что я уверен в его полезности, почему и делюсь им с Сетью. :)

Если кому-то план покажется очевидным, рад за вас. Если нет — то вот он, пожалуйста.

  1. С чем связана работа + что было выполнено. (2 предложения)

    «Моя работа связана с...»

    «В её рамках было разработано...»

    Во введении достаточно лишь назвать ключевые слова, по которым потенциальный слушатель поймёт а) разбирается ли он в теме вообще и б) интересна ли ему эта тема.

  2. Предметная область. (2 предложение)

    Скорее всего, в одно предложение уложиться не удастся, поэтому выделяется два.

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

    Скажем, если написали «Моя работа связана с разбором s-выражений при помощи многопоточных компиляторов на языке Haskell», значит, в одном предложении рассказываем, что такое s-выражения, в другом — что такое многопоточные компиляторы (раскрываем только самые сложные/конкретные концепции, на всё времени нет)

  3. Задача. (1 предложение)

    «Решалась задача...» или «Была поставлена задача...»

    Одним предложением описываем поставленную задачу, так как контекст уже установлен.

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

    Я многократно видел примеры того, как единственным вопросом из зала было: «А как полученные вами результаты соответствуют вашей задаче?».

  4. Актуальность. (1 предложение)

    «Данная задача имеет ценность, потому что...»

    Актуальность означает одно из двух: а) какую пользу принесёт решение вашей задачи или б) какие трудности существуют из-за того, что данная задача до сих пор не была решена.

  5. Методы. (3 предложения)

    «Для решения задачи было использовано...»

    Ни в коем случае нельзя углубляться в подробности, потому что аудитория гарантированно уснёт. Они не занимались этой работой, им априори не так интересны детали.

    Если кому-то станет действительно интересно, он спросит после доклада.

  6. Результаты + тесты. (2 предложения)

    «В качестве конечного результата было получено...»

    «Работа оттестирована на...» или «В качестве тестовых данных были использованы...»

    В качестве конечного результата всегда должен получиться некий артефакт: компьютерная программа, формальная спецификация, механизм/агрегат, какая-то ещё вещь.

    Очень большой интерес всегда вызывает то, на каких данных/экспериментах была опробована работа. Также тот факт, что в процессе работы автор озаботился тем, как он будет проверять её корректность, повышает её ценность.

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

  7. Выводы. (1 предложение)

    «Полученные результаты позволяют сделать вывод о том, что...»

    Всегда спрашивают о выводе, если сам об этом не сказал.

    Если вывод не сформировался сам собой в процессе работы, то ничего из пальца высасывать не нужно: достаточно просто сказать, что обнаружено, что выбранные методы успешно решают поставленную задачу / непригодны для решения поставленной задачи.

  8. Где может использоваться. (1 предложение)

    «Эта работа может быть использована для...»

    Если ответ на этот вопрос не очевиден из вышесказанного, то его обязательно зададут из зала.

    Увы, если результаты работы или использованные методы решения не могут использоваться нигде, то это автоматически означает, что работа не нужна, а, следовательно, зачем она вообще была выполнена? И потом, если есть актуальность, то почему работу негде использовать?.. В любом случае, вскрывается серьёзная логическая ошибка в постановке задачи.

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

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

Context-dependent Behat tests steps

Preamble is this: we have the PHP-based website, and we are testing it with the Behat+Mink+MinkExtension combo.

Suppose we want to write the following test scenario:

When I am in the Friends section

… (something there) …

Then I should see “My Friend” in search results

Let's define this steps in our FeatureContext. First step we can define with the following regexp: /^I am in the Friends section$/ because we really don’t need the method of FeatureContext class containing long switch enumerating every possible section of the site.

 * @Given /^I am in the "Friends" section$/
public function iAmInTheFriendsSection() {
 return new Given('I am on "/friends"');

Second step we can define with the following regexp: /^I(?: should)? see "([^"]*)" in the search results$/.

 * @Then /^I should see "([^"]*)" in the search results$/
public function iShouldSeeInTheSearchResults($search_term) {

 // separate helper function to search the "search results" HTML element
 $search_results = $this->getSearchResultsElement();

 // separate helper function to search the $search_term text in $search_results element
 $this->trySearchTextOnDomElement($search_term, $search_results);


It should be obvious why we use the custom test step instead of using the predefined test steps and writing something like 'I should see "My Friend" in ".search-wrapper form input[role="search"]" element'.

Then, someday, sure thing, we will want to write the following scenario:

When I am in the Shop section

… (something there) …

Then I should see “Interesting product” in search results

And in here, we have another “search results”, which should be found by completely different selector and which is located on different page.

So, this is the context-dependent statement: term “search results” depends on what “section” we mentioned previously. This is right from the linguistics. To be able to use this natural-language feature we need to implement it somehow.

I'll use the abbrev CDTS instead of longer "context-dependent test step".

Fortunately, Behat has a feature with exactly the same purpose: subcontexts. Unfortunately, it's not working in the way we need to use the CDTS properly.

In an ideal world, we can do this:

* @Given /^I am in the Friends section$/
public function iAmInTheFriendsSection() {
 $this->useContext('friends_section', new FriendsSectionContext())
 return new Given('I am on "/friends"');

and this would load the FriendsSectionContext and all CDTS definitions in it, like the following:

// in FriendsSectionContext class
 * @Then /^I should see "([^"]*)" in the search results$/
public function iShouldSeeInTheSearchResults($search_term) {
 // This selector is valid only in the context of "Friends" section.
 $search_results_selector = '#PeopleFinder #pf_all .results';

 // Logic to check if the given $search_term is the present in anything called "search results" in the context of "Friends" section.
 $search_results = $this->getSession()->getPage()->find('css', $search_results_selector);
 $search_term_present = strpos($search_results->getHtml(), $search_term);
 if ($search_term_present === false) {
  throw new Exception(...);

We useContext different context class, we get different definition for the /^I should see "([^"]*)" in the search results$/ test step.

Unfortunately, Behat cannot load the test step definitions from subcontexts at runtime. Apparently, it’s because it should parse the regexps in docblocks corresponding to definitions or something like that. So, are forced to load all our subcontexts right in our constructor.

Apart from being horribly ineffective, this prevents us from defining the test steps having same regexp across several different separate subcontexts.

Workaround for this problem is this:

  1. add the property to the FeatureContext which will hold the reference to current subcontext, name it like "location_context" or so,
  2. make the context-setting ('I am in the "..." section') test step set the "location_context" to the subcontext needed (you can get the subcontext with the call to getSubcontext('alias')),
  3. move the context-dependent logic to “normal” subcontext methods, which should have the same name across all subcontexts,
  4. register all subcontexts with useContext under meaningful aliases like "friends_section", "shop_section", etc,
  5. define the context-dependent test step like 'I should see "..." in search results' in main FeatureContext class,
  6. in the definition of this step, get the context-dependent logic needed by calling the relevant method on the subcontext the "location_context" property currently points at.

So, we need our context-setting test steps to be like this:

 * @Given /^I am in the "Friends" section$/
public function iAmInTheFriendsSection() {
 $this->location_context = $this->getSubcontext('friends_section');
 return new Given('I am on "/people"');

Assuming 'friends_section' is an alias of the FriendsSectionContext, and it was set in the constructor, after this test step, our "location_context" will be FriendsSectionContext, and, say, it's getSearchResultsElement() will do exactly what we need in the "Friends" section.

Then, the context-depentent test step will be like this, getting the location-dependent logic from the "location_context" set previously:

 * @Then /^I should see "([^"]*)" in the search results$/
public function iShouldSeeInTheSearchResults($search_term) {
 // getSearchResultsElement() is defined in subcontext which was set before in $this->location_context
 $search_results = $this->location_context->getSearchResultsElement();
 $search_term_present = strpos($search_results->getHtml(), $search_term);
 if ($search_term_present === false) {
  throw new Exception(...);

Main point is this: we want to check if something should appear in the "search results" entity in some different page → we can use the same test step in our .feature files, just explicitly name the section needed beforehand somewhere above in the text. This will make the .feature files a lot more human-readable.

This concludes the explanation about how to use this linguistic technique in Behat tests.

вторник, 3 июля 2012 г.

Как добавить поддержку Markdown в Kate, KWrite и встроенный редактор Krusader (F4)

Лично я для работы с PHP проектами уже давно пользуюсь в качестве IDE исключительно Krusader'ом и его встроенным редактором. И встроенным поиском. И встроенным архиватором. Ну, в общем, понятно. Встроенный в Krusader редактор является всего лишь Kate, который запихнули в контейнер. И этот редактор меня устраивает практически во всём, кроме одной вещи: там нет встроенной поддержки Markdown.

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

понедельник, 2 июля 2012 г.

Использование Qt для реализации GUI в Common Lisp

У меня нет никаких предрассудков насчёт Qt и на десктопе у меня KDE, так что для первых шагов в GUI я воспользовался проектом под названием CommonQt. Это CFFI-биндинги к libqt и libsmoke.

Здесь я запишу, как настроить окружение для того, чтобы писать Qt-приложения. Ниже скриншот конечного результата. ;)

четверг, 14 июня 2012 г.

Простое и сложное в Common Lisp

Сегодня развлекался с моим любимым Common Lisp и обнаружил интересную деталь.

Допустим, мы хотим написать простую функцию для определения среднего арифметического набора заданных чисел. Чтобы она работала вот так:

(average 1 2 3 4)

...ну хорошо, хорошо:

(coerce (average 1 2 3 4) 'float)

Заметьте то, что я не хочу передавать в average список, я хочу кортеж чисел. Интересная деталь вот в чём: самая простая (в смысле, лаконичная) реализация, которая пришла мне в голову, это такая:

(defun average (&rest args)
  (/ (reduce #'+ args) (list-length args)))

Дело в том, что определение среднего арифметического — это задачка для средней школы, и мы вполне можем её дать в качестве задания по программированию на второй паре (на первой мы изучим весь синтаксис лиспа вместе с макросами defun и defvar). Но на самом деле мы не можем её дать, потому что у нас есть вот эта форма в решении: (reduce #'+ args), и, чтобы студенты смогли ей бегло пользоваться, нужно, чтобы они знали как минимум:

  • о разделении пространства имён переменных и пространства имён функций;
  • как передавать в функцию имя другой функции;
  • о назначении и работе reduce, что для не знакомого с функциональными методами, вообще говоря, стопроцентная магия (особенно если их уже обработали алголоподобными языками);
  • о том, что параметры, переданные через &rest, оборачиваются в список.

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

Конечно, мы можем соорудить нечто работающее из let и dotimes, но тогда зачем нам Common Lisp вообще? :)

четверг, 7 июня 2012 г.

Паттерн «Веб-функция»

Допустим, вам нужно написать endpoint для аякс-запроса. Или обычный обработчик обычного POST-запроса от веб-формы. Тогда основные действия, которые обязательно должны быть в процессе обработки, будут такие:

  • Фильтрация входных данных (это НЕ валидация бизнес-правилами, только лишь такие действия, как укорачивание, очистка от окружающих пробелов, исключение непечатных символов, приведение к нижнему регистру и т. д.). Или мы можем даже сыграть роль адаптера и переименовать некоторые параметры запроса в вид, ожидаемый обработчиком.
  • Обработка запроса. Здесь мы собственно делаем то, зачем существуем. Мы ожидаем уже более-менее очищенный от мусора массив входных параметров, возможно, среди которых есть ошибочные (например, числовое значение вне допустимого диапазона, или передан массив вместо скаляра).
  • Форматирование результата для отклика клиенту. Например, мы можем возвращать JSON, или генерировать HTML страницу по шаблону. Или генерировать изображения. Или отдавать файлы с диска.

Если записать в функциональном стиле на PHP, то можно получить следующий шаблон хэндлера:


echo format(process(clean($_REQUEST)));

 * Здесь очистка суперглобального массива $_REQUEST и генерация массива $request
 * @param array $request Данные запроса из массива $_POST или $_GET (или $_REQUEST).
 * @return array Очищенные данные из запроса, которые ожидает функция process.
function clean($request)
  // TODO
  return $request;

 * Здесь выполнение действий согласно запросу $request
 * @param array $request Параметры запроса, очищенные функцией clean.
 * @return array $result Результат работы со всей информацией, необходимой для форматирования ответа клиенту.
function process($request)
  // TODO
  return $request;

 * Здесь форматирование результата работы для выдачи клиенту.
 * @param array $result Результат работы, как он сгенерирован процессором. Нам не дозволяется использовать какие-либо другие данные.
 * @return string Форматированный ответ, готовый к отправке клиенту через echo.
function format($result)
  // TODO
  return json_encode($result);

Если process сталкивается с ошибкой, он пишет об этом в результат своей работы (например, в поле 'error') и сразу возвращает результат в format.

Если format должен генерировать большой HTML документ, то ничего страшного, он может и делать echo внутри себя, вместо того, чтобы возвращать строку. Тогда вызов всей цепочки будет без echo в начале, конечно же.

Как запускать программы на Common Lisp как консольные скрипты

Допустим, у вас есть классная маленькая программка на Common Lisp под названием filter. Она занимает всего один файл исходного кода под названием filter.lisp примерно в 200 строк длиной. В этом файле вот такой заголовок:

(defpackage :localhost.me.filter
  (:use :common-lisp)
  (:export :run))
(in-package :localhost.me.filter)

И дальше собственно сама программа. Допустим также, что вы пользуетесь своей программой часто. Загвоздка в том, что для того, чтобы её запустить, вам необходимо сделать слишком много действий:

$ sbcl
CL-USER> (load "filter.lisp")
CL-USER> (:localhost.me.filter:run)

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

Решение первое

SBCL умеет запускать программы на CL в режиме скриптов командной строки, для этого используется специальный флаг --script. Более того, благодаря этому можно написать лисп-программу, добавить к ней типичный юниксовый шебанг, вызывающий SBCL с этим флагом, и она будет работать как любой другой шелл-скрипт (за исключением того, что рантайм SBCL весит 50MB бгг). Так что напишем этот скрипт:

#!/usr/bin/sbcl --script
(load "filter.lisp")

Теперь положим этот скрипт в ту же директорию, что и filter.lisp, в файл с именем run и можно будет запускать нашу программу так:

$ ./run

Что и требовалось.

Решение второе

Однако, у флага --script есть одна особенность: SBCL не загружает никакие пользовательские скрипты инициализации при старте, так что скрипт будет выполняться в 100% дефолтном рантайме. Это плохо, если, допустим, вы используете Quicklisp и у вас в filter.lisp есть такой вызов:

(ql:quickload :CL-FAD)
(use-package :CL-FAD)

Ну или вызов любой другой библиотеки, неважно. Если запустить такой скрипт первым способом, то SBCL будет грязно ругаться по поводу того, что он не знает, что такое ql:quickload.

Для решения этой проблемы мы смухлюем, подменив бинарник SBCL таким бинарником, у которого все нужные библиотеки уже загружены. В SBCL есть функция save-lisp-and-die, которая выгружает текущее состояние рантайма в файл с указанным именем. Дальше этот файл можно использовать как обычный бинарник SBCL, в том числе, и для вызовов с использованием --script. Поэтому если запустить SBCL и сразу выгрузить его в файл, то полученный бинарник будет содержать всё, что SBCL подключил при загрузке, основываясь на пользовательских конфигах.

(save-lisp-and-die "/абсолютный/путь/до/файла")

Теперь в скрипте run, который написан по первому варианту, заменяем путь до системного SBCL путём до выгруженного бинарника, и скрипт начнёт работать как должен.

Как писать юнит-тесты к программе на Free Pascal при помощи FPTest

В свете работы над моими старыми программами из КГУ понадобилось покрывать код юнит-тестами. Как выяснилось после гуглопоиска, для Free Pascal, которым я компилирую свою переработку, существует проект под названием FPTest, представляющий собой каркас для написания юнит-тестов на этом замечательном языке.

Документации по этому проекту довольно немного, и официальная вики, как и README, сильно Lazarus-ориентированы. Поэтому расскажу здесь, как подключить FPTest к существующему консольному проекту и собрать тесты, написанные с его помощью.

среда, 23 мая 2012 г.

PHP: архивы PHAR не работают с включённым suhosin patch

Сегодня ставил behat и ужаснулся. phar-архивы не запускаются в командной строке вообще никак. Даже не появляется сообщения об ошибке, просто молча ничего не выполняется.

После некоторого продолжительного поиска в Сети был найден ответ на ServerFault: выполнение phar-архивов блокирует suhosin patch.

Для решения проблемы нужно раскомментировать следующую строку в /etc/php5/cli/conf.d/suhosin.ini:

;suhosin.executor.include.whitelist =

И заменить её на

suhosin.executor.include.whitelist = "phar"

Одноразовое решение выглядит так:

$ php -d suhosin.executor.include.whitelist="phar" MYPHAR.phar

воскресенье, 13 мая 2012 г.

Рефакторинг заданий курса КГУ по программированию 2004-2011 гг.

У нас в набережночелнинском филиале КГУ на факультете прикладной математики и информационных технологий преподавали несколько программистских курсов. На них студентам задавали задания в духе "перемножьте каждый четвертый член массива чисел полученной от пользователя длины", и их нужно было запрограммировать. В первом семестре - на Delphi. Потом - на C++. После третьего курса можно было писать на чём хочешь.

Пожалуй, это были единственные учебные курсы из всех пяти лет обучения, которые хоть что-то дали лично мне как разработчику. Во всяком случае, я узнал об ООП в стиле C++ и о такой вещи, как указатели.

Я ужасно обожаю чистить чужой код, поэтому мне пришла в голову идея поднять старые исходники готовых решений тех задач и переоформить их согласно всему тому, чему я научился за последние 7 лет практики (да, включая универ, конечно :) ).

Изменения, которые я хочу внести, следующие:

  1. Структура каждой программы должна быть как можно проще. Всё должно компилироваться из командной строки. Всю IDE машинерию нужно убрать.
  2. Для сборки будет использоваться GNU Make, для программ на любом языке.
  3. Покрыть код юнит-тестами.  Чистый TDD использовать уже поздно, всё-таки код уже написан.
  4. Код единообразно переформатировать, очистить от "попахивающих" мест и снабдить формальными комментариями.
  5. Текст заданий изъять изо всяких PDF и DJVU файлов, в которых он сейчас находится, и перенести в текстовые README.md.
Кроме того, для разработки на первых курсах активно использовался homemade хак для того, чтобы выводить кириллический текст в консоль. Отдельный хак для программ на Паскале, отдельный для программ на Си. Общаться с пользователем по-русски не так важно, так что для простоты все сообщения от программ будут переведены на английский.

В первом семестре использовался Delphi, с помощью которого компилировались консольные приложения. Вместо Delphi будет использоваться Free Pascal в объектном режиме. Всю машинерию, связанную с Delphi, нужно убрать.

Для тестов программ на Паскале будем использовать FPTest.

Начиная со второго семестра и дальше использовался C++. Для компиляции точно буду использовать g++ вместо Visual Studio, а вот какой каркас для тестов использовать, пока не знаю (просто пока не пробовал xUnit-каркас для C++).

Исходники уже выложил на ГитХабе. Пока более-менее нормально обработано только первое задание.

Думаю, когда закончу задания первого семестра, напишу письмо в КГУ. :)

понедельник, 23 апреля 2012 г.

Мысли о третьем шаге в TDD

Сегодня второй раз в жизни пишу код согласно чистому TDD, прямо как заповедали такие мастера, как Роберт Мартин, Эндрю Хант и Майкл Физерс. Один случай меня заставил серьёзно задуматься о разнице в подходах к разработке.

Третий шаг в TDD гласит: «напиши ровно столько кода, сколько нужно для того, чтобы проваливающийся ранее тест начал завершаться успешно». (Первые два это «напиши проваливающийся тест» и «заставь этот тест компилироваться»). Несмотря на то, что он кажется довольно простым, пункт насчёт «ровно столько, сколько нужно» невероятно важен в рамках этой методологии.

Этот пункт, среди прочего, означает, что, если твой тест не предусматривает обработку ошибочных данных — значит, тебе нельзя писать такую обработку в коде. Если твой тест только проверяет тип результата функции, но не корректность его значения — значит, тебе нельзя писать код, который генерирует реальные правильные данные.

Только после раздумья длиной что-то около полудня я понял, почему так важно соблюдать это правило, хотя ответ очень прост. Если ты напишешь код, который не предусматривают твои тесты, пусть даже он будет выполнять какую-то идейно полезную работу (а он, конечно же, будет выполнять идейно полезную работу), он не будет покрыт тестами. А код, который не покрыт тестами — это legacy код, которому положено гореть в аду. Более того, если ты оставишь весь этот код среди другого, чистого и покрытого тестами, он будет, как ржавая арматура, мешать добавлять новую функциональность. Так как он не покрыт тестами, а ты в целом стараешься следовать TDD, значит, все эти буквы становятся просто шумом, про полезность и надёжность которого ты не знаешь ничего.

Наверное, теперь я на самом деле понял, насколько иной философией разработки пользуюсь. Сегодня я удалил целый класс, почти 100 строк кода, который написал, пытаясь закрыть один проваливающийся тест, и ни разу не пожалел об этом. Скорее наоборот: вспоминая ту ужасную массу кода, я уверен, что моя программа стала гораздо лучше, лишившись её.

четверг, 19 апреля 2012 г.

Подключение к Windows VPN серверу из Debian

О-хо-хо, как же я давно не писал!..

Есть важная особенность при настройке VPN соединения к Windows-серверу из Debian. Это если не считать того, что на самом деле это называется не VPN, а PPTP подключение, ну да простим Майкрософту эту мелочь.

При настройке подключения, следует указать, что разрешён только один метод аутентификации: MSCHAPv2. И включить MPPE шифрование. Спасибо orbnajes с форумов Fedora за объяснение.

Без этих действий VPN подключение будет молча обрываться, и сообщение об ошибке в syslog'е мало поможет.

update: Ещё, возможно, придётся уменьшить MTU на сетевой карте, или подключению будет очень дурно, и оно регулярно будет обрываться. Для того сервера, который мне был нужен, сработало уменьшение MTU с 1500 до 800.

понедельник, 23 января 2012 г.

Настройка отправки почты с локального Debian сервера через exim + gmail

Прикручивал некоторую функциональность на сайт, который развёрнут на моей локальной машине, и понадобилось проверить, в каком виде отправляются письма с сайта. А почтового сервера-то на моей машине и нету!

Воспользовавшись учёткой на гуглопочте и руководством в Debian Wiki, мне удалось всё настроить за ~5 минут, чего и вам желаю. Авторы руководства, большое вам человеческое спасибо.

Напомню, что в стандартной поставке Debian Squeeze (который уже я частично переделал на Wheezy) устанавливается MTA exim4, настроенный на исключительно локальную доставку почты.

четверг, 12 января 2012 г.

Видеодрайверы для Debian на Dell Inspiron N5110

После переезда на новый Dell Inspiron N5110 выяснилась неприятная новость: в нём установлены две видеокарты, работающие по технологии NVidia Optimus, и в Debian она официально не поддерживается. Все эти приятные вещи я узнал из поста на форуме убунтоводов насчёт NVidia Optimus.

Я отключил себе видеокарту NVidia, следуя рекомендациям robbyx.net (пункт #3), и буду следить за жизнью проекта Bumblebee, посвящённому реализации NVidia Optimus на Linux. Возможно, придётся даже включиться в разработку этого проекта, а то мне очень хочется быстрый OpenGL у себя на ноуте в Debian.

Программирование компьютерных игр в Common Lisp при помощи lispbuilder-sdl

Сегодня сбылась мечта идиота: нашёл пакет для графики в Common Lisp, с помощью которого можно рисовать игры с графикой и звуком. Это не GUI пакет с виджетами, это 2D/3D канва и инициализаторы для звука, джойстика и CDROM. Пакет называется lispbuilder-sdl, и он крут.

Как обычно, напомню для себя, как lispbuilder-sdl подключается в SBCL.

четверг, 5 января 2012 г.

Интернационализация MySQL 5.1

Сегодня меня вконец достала дефолтная настройка на latin1 в MySQL, и я пошёл курить маны. Хочется, естественно, перевести всё в utf8. Решается, как оказалось, всё тремя строчками в конфиге.