кто на ком стоял
Friday, 27 June 2014 17:56А расскажите за эту вашу оопню. Есть класс Cup, обеспечивающий некоторый контейнер. Из содержимого контейнера строится объект класса Coffee. То есть в некотором смысле Cup содержит Coffee. Но, с другой стороны, верно и что Coffee налито в Cup.
Как идеологически правильно:
а) cup.coffee = Coffee(...)
Чашка знает где достать порошок, и пусть сама думает, как его заваривать.
б) coffee.cup = Cup(..)
И пусть кофе само дальше заваривается, используя чашку.
в) controller.cup = Cup(...)
controller.coffee = Coffee(...)
Попросим кофе сериализоваться и покажем чашке:
coffeestring = controller.coffee.toString()
controller.cup.put(coffeestring)
Как идеологически правильно:
а) cup.coffee = Coffee(...)
Чашка знает где достать порошок, и пусть сама думает, как его заваривать.
б) coffee.cup = Cup(..)
И пусть кофе само дальше заваривается, используя чашку.
в) controller.cup = Cup(...)
controller.coffee = Coffee(...)
Попросим кофе сериализоваться и покажем чашке:
coffeestring = controller.coffee.toString()
controller.cup.put(coffeestring)
удобное рабочее место
Wednesday, 10 April 2013 04:19Уже сто раз замечал, насколько удобнее, скажем:
- сидя на полу, поставив ноут на диван
или
- забравшись на диван с ногами, ноут на коленях
или даже
- стоя на кухне, ноут на холодильнике
То есть вот админские задачи, или там, переписка всякая, оно конечно и за столом вполне ок. А вот именно писать код за столом довольно мучительно. Существенная часть неких внутренне-инфраструктурных вещей была придумана и реализована как раз в одной из этих нелепых поз.
С вами этого не бывает?
- сидя на полу, поставив ноут на диван
или
- забравшись на диван с ногами, ноут на коленях
или даже
- стоя на кухне, ноут на холодильнике
То есть вот админские задачи, или там, переписка всякая, оно конечно и за столом вполне ок. А вот именно писать код за столом довольно мучительно. Существенная часть неких внутренне-инфраструктурных вещей была придумана и реализована как раз в одной из этих нелепых поз.
С вами этого не бывает?
Чота наслушавшись Pulse малоизвестных британцев Pink Floyd, дёрнуло меня вытащить Odyssey One Compilation девяносто шестого года. Давненько не слушал. И оно неиллюзорно клёво.
Собственно, вот, с трека #03 Venus Rapsody и до #07 Prophet of Evil включительно, двадцатиминутный кусок, - это одно из самых прекрасных мест вообще во всей музыке, которую слышал.
Кроме того, это весьма и весьма атмосферненько для ночной работы.
Вторая штука, котируемая на том же уровне - это фигурно порезанный Dreamland Майлса.
Да, у меня есть претензии к композиции альбома. Я слушаю его в таком виде:
( Треклист от gns )
Честно, никогда не понимал, за каким хером после In my dreams вхерачили вокальный One and one, и терпеть не могу вокальную версию Fable.
Собственно, вот, с трека #03 Venus Rapsody и до #07 Prophet of Evil включительно, двадцатиминутный кусок, - это одно из самых прекрасных мест вообще во всей музыке, которую слышал.
Кроме того, это весьма и весьма атмосферненько для ночной работы.
Вторая штука, котируемая на том же уровне - это фигурно порезанный Dreamland Майлса.
Да, у меня есть претензии к композиции альбома. Я слушаю его в таком виде:
( Треклист от gns )
Честно, никогда не понимал, за каким хером после In my dreams вхерачили вокальный One and one, и терпеть не могу вокальную версию Fable.
(no subject)
Tuesday, 15 January 2013 10:09Вот оно чо.
В monitor видно, что херню делает:
Зато вот такое, без вотча:
Делает:
Всё правильно. Тогда какого?
Можно сперва запайпить, а потом уж ставить вотч и экзекать. Это работает:
И я бы даже смирился, да вот беда - их собственный враппер делает сначала watch, а потом отдаёт пайп для работы:
( Read more... )
[altlinux@eeelive ~]$ python -c 'import redis, time; r=redis.Redis(); p=r.pipeline(); p.watch("abc"); p.set("123", 1); time.sleep(5); p.execute()'
В monitor видно, что херню делает:
1358237927.046987 [0 127.0.0.1:51442] "WATCH" "abc"
1358237927.048088 [0 127.0.0.1:51442] "SET" "123" "1"
1358237932.053436 [0 127.0.0.1:51442] "MULTI"
1358237932.053582 [0 127.0.0.1:51442] "EXEC"
1358237932.054853 [0 127.0.0.1:51442] "UNWATCH"
Зато вот такое, без вотча:
$ python -c 'import redis, time; r=redis.Redis(); p=r.pipeline(); p.set("123", 1);time.sleep(2); p.execute()'
Делает:
1358237753.861087 [0 127.0.0.1:51420] "MULTI"
1358237753.861234 [0 127.0.0.1:51420] "SET" "123" "1"
1358237753.861319 [0 127.0.0.1:51420] "EXEC"
Всё правильно. Тогда какого?
Можно сперва запайпить, а потом уж ставить вотч и экзекать. Это работает:
$ python -c 'import redis, time; r=redis.Redis(); print r.get("abc"); p=r.pipeline(); p.set("abc", 1); p.watch("abc"); time.sleep(5); p.execute(); print r.get("abc");'
None
1
===============================
1358238142.575640 [0 127.0.0.1:51474] "GET" "abc"
1358238142.577093 [0 127.0.0.1:51474] "WATCH" "abc"
1358238147.579733 [0 127.0.0.1:51474] "MULTI"
1358238147.579892 [0 127.0.0.1:51474] "SET" "abc" "1"
1358238147.579977 [0 127.0.0.1:51474] "EXEC"
1358238147.581572 [0 127.0.0.1:51474] "UNWATCH"
1358238147.582558 [0 127.0.0.1:51474] "GET" "abc"
И я бы даже смирился, да вот беда - их собственный враппер делает сначала watch, а потом отдаёт пайп для работы:
( Read more... )
(no subject)
Tuesday, 27 November 2012 21:00Дебажил я распределённый код. Дебажил-дебажил, читал-перечитывал, в конце концов плюнул на всё и вкрутил мегаизбыточное логирование проходящих данных на каждом шаге обработки, туда же всякие проверки-хуерки, и запустил всё это, предвкушая как сейчас точно локализую проблему.
И случилось страшное. Ошибки перестали появляться.
И случилось страшное. Ошибки перестали появляться.
забыть бейсик
Wednesday, 22 August 2012 01:30На тридцать первом году жизни и двадцатом программирования, со мной случилось это.
Глядя на собственный говнокод такого типа:
- я вдруг перестал понимать, зачем вся эта унылота и почему не сделать просто:
Ведь дальше-то этот лист большой опять кто-то будет сканить, опять формировать новый лист, опять кому-то отдавать. Так зачем? Пусть вся йилдят! Корутины! А в промежутках каждый, возможно, успеет ещё что-нибудь где-нибудь отметить, что тоже хорошо.
Это прорыв в сознании, я щетаю. Как говаривал Владимир Леви: - повелело освободить новый код от школярской линейной последовательности. И, далее там же: - я эту прямоходность «системы» неосознанно почитал за достоинство, с детства вбита. Пытался, с малым успехом, протащить сквозь весь код, протоптать магистраль.
И вроде ж знаешь уже много разных вещей, - серьёзно занявшись программированием после нескольколетнего перерыва, ничего, впрочем и к сожалению, не отнявшего от предварительного пути бейсик=>паскаль=>фокспро=>qt=>пхп (шелл сюда не включаю по ряду очевидных причин). Но вот чтоб сразу мыслить в этих терминах...
Глядя на собственный говнокод такого типа:
def getitems(workers):
items = []
for w in workers:
items.extend(get_worker_items(w))
return items
- я вдруг перестал понимать, зачем вся эта унылота и почему не сделать просто:
def getitems(workers):
for w in workers:
for i in get_worker_items(w):
yield i
Ведь дальше-то этот лист большой опять кто-то будет сканить, опять формировать новый лист, опять кому-то отдавать. Так зачем? Пусть вся йилдят! Корутины! А в промежутках каждый, возможно, успеет ещё что-нибудь где-нибудь отметить, что тоже хорошо.
Это прорыв в сознании, я щетаю. Как говаривал Владимир Леви: - повелело освободить новый код от школярской линейной последовательности. И, далее там же: - я эту прямоходность «системы» неосознанно почитал за достоинство, с детства вбита. Пытался, с малым успехом, протащить сквозь весь код, протоптать магистраль.
И вроде ж знаешь уже много разных вещей, - серьёзно занявшись программированием после нескольколетнего перерыва, ничего, впрочем и к сожалению, не отнявшего от предварительного пути бейсик=>паскаль=>фокспро=>qt=>пхп (шелл сюда не включаю по ряду очевидных причин). Но вот чтоб сразу мыслить в этих терминах...
(no subject)
Friday, 30 March 2012 14:51> задача о разработке метода определения делимости числа на 15, при условии, что запись числа настолько длинная, что не влезает ни в один целочисленный тип (т.е., грубо говоря, делить напрямую нельзя).
http://smartsourcing.ru/blogs/kurilka/1364
Очевидно, что необходимо и достаточно выполнение двух условий:
а) последняя цифра 5 или 0
б) сумма цифр (aka X mod 9) делится на три. Свойста этой штуки таковы, что на самом деле полную сумму считать не нужно: достаточно складывать цифры по одной, и как только результат становится двузначным - останавливаться, делить на 9 с остатком, продолжать.
(вообще, порог 20 здесь произвольный. Можно и 100 взять - чтоб не слишком часто делить)
http://smartsourcing.ru/blogs/kurilka/1364
Очевидно, что необходимо и достаточно выполнение двух условий:
а) последняя цифра 5 или 0
б) сумма цифр (aka X mod 9) делится на три. Свойста этой штуки таковы, что на самом деле полную сумму считать не нужно: достаточно складывать цифры по одной, и как только результат становится двузначным - останавливаться, делить на 9 с остатком, продолжать.
def mod9(n): sum = 0 for x in n: sum+=int(x) if sum>20: sum = sum % 9 return sum # x задано как строка if x[-1] in ('0', '5') and mod9(list(x)) % 3 == 0: print "yes' else: print "no"
(вообще, порог 20 здесь произвольный. Можно и 100 взять - чтоб не слишком часто делить)
(no subject)
Wednesday, 28 December 2011 22:57сижу вот это, ковыряюсь с среднеквадратичными и прочими экспоненциальными скользящими, да и думаю что надо было тот семестр статистики досидеть всё же. Чтоб представлять, что как, чего на самом деле можно ожидать, и какие артефакты могут вылезти.
Хотя, с другой стороны, сотрудники неподалёку честно учили, но им на данный момент вся эта байда кажется ещё более мутной чем мне. Оно ж когда понадобится, а бывает это не так часто, тут и оказывается что эээ, ммм, вроде чота десять лет назад слышал :)
Ну и я "слышал".
Итак. Есть некоторый цикл, в котором происходит нечто. Интересно время исполнения этого цикла и длительность его отдельных этапов. То есть, по окончании каждой итерации имеем набор (total=27.3, stage1=20.17, stage2=6.43, stage3=0.02, stage4=0.63). Некая штука время от времени запрашивает эти циферки. Существенно реже, чем происходят сами итерации (по наблюдениям, одна итерация может занять от 10 до 40 секунд).
Я бы с удовольствием напрямую пушил в момент окончания циферки с таймстампом, но давайте попробуем обойтись без этого. При таком раскладе, конечно, мы часть информации теряем, и это ок. Главное, не потерять что-нибудь важное и интересное (~ общие тренды + аномалии).
Опуская всю проведённую теоретическую и экспериментальную исследовательскую работу, схема такова:
- тайминг каждой итерации записывается в кольцевой буфер размером 5.
- при этом сразу записывается в специально отведённое место пара (ring[-1], qaverage(ring, exp)) - значение из последней итерации и среднеквадратичное взвешенное по e^i:
def qaverage(ring, weight_function = lambda x: 1):
s = sum([ring[i]*ring[i]*weight_function(i) for i in xrange(len(ring))])
d = sum([weight_function(i) for i in xrange(len(ring))])
return sqrt(s/d)
То есть, значимость самых новых элементов больше.
На графике рисуется это самое значение и соответствующее ему qaverage (очевидно, бегающее где-то около, выше или ниже, самого значения). Из постановки задачи ясно, что бОльшая часть этих пар будет благополучно профачена - забирается консумером, грубо говоря, каждая n-я при случайном n из [2,7].
То есть общее состояние "примерно" видно. Конкретные высокие значения могут никогда не попасть в график, но оставят след в qaverage, хотя сам qaverage конечно ихникогда не достигнет только если все пять последних будут одинаковые. В принципе, их можно будет ручками посмотреть в логе :) И понятно, что если qaverage больше выхваченного значения, то до него были цифры выше, и наоборот. На самом деле я бы увеличил длину буфера до 10, и показывал дополнительно qaverage от (последней) половины сохранённых данных. Или можно обойтись одним числом (каким)?
В общем, природа данных известна, граничные условия известны - ругайте :)
ЗЫ. Вопросы вида "почему среднеквадратичное?" и "почему e^i?" лучше формулировать в виде "надо использовать XXX потому что А, B, C".
ЗЗЫ. C реализацией exponential moving average возиться пока не стал ибо не уверен что есть смысл.
Хотя, с другой стороны, сотрудники неподалёку честно учили, но им на данный момент вся эта байда кажется ещё более мутной чем мне. Оно ж когда понадобится, а бывает это не так часто, тут и оказывается что эээ, ммм, вроде чота десять лет назад слышал :)
Ну и я "слышал".
Итак. Есть некоторый цикл, в котором происходит нечто. Интересно время исполнения этого цикла и длительность его отдельных этапов. То есть, по окончании каждой итерации имеем набор (total=27.3, stage1=20.17, stage2=6.43, stage3=0.02, stage4=0.63). Некая штука время от времени запрашивает эти циферки. Существенно реже, чем происходят сами итерации (по наблюдениям, одна итерация может занять от 10 до 40 секунд).
Я бы с удовольствием напрямую пушил в момент окончания циферки с таймстампом, но давайте попробуем обойтись без этого. При таком раскладе, конечно, мы часть информации теряем, и это ок. Главное, не потерять что-нибудь важное и интересное (~ общие тренды + аномалии).
Опуская всю проведённую теоретическую и экспериментальную исследовательскую работу, схема такова:
- тайминг каждой итерации записывается в кольцевой буфер размером 5.
- при этом сразу записывается в специально отведённое место пара (ring[-1], qaverage(ring, exp)) - значение из последней итерации и среднеквадратичное взвешенное по e^i:
def qaverage(ring, weight_function = lambda x: 1):
s = sum([ring[i]*ring[i]*weight_function(i) for i in xrange(len(ring))])
d = sum([weight_function(i) for i in xrange(len(ring))])
return sqrt(s/d)
То есть, значимость самых новых элементов больше.
На графике рисуется это самое значение и соответствующее ему qaverage (очевидно, бегающее где-то около, выше или ниже, самого значения). Из постановки задачи ясно, что бОльшая часть этих пар будет благополучно профачена - забирается консумером, грубо говоря, каждая n-я при случайном n из [2,7].
То есть общее состояние "примерно" видно. Конкретные высокие значения могут никогда не попасть в график, но оставят след в qaverage, хотя сам qaverage конечно их
В общем, природа данных известна, граничные условия известны - ругайте :)
ЗЫ. Вопросы вида "почему среднеквадратичное?" и "почему e^i?" лучше формулировать в виде "надо использовать XXX потому что А, B, C".
ЗЗЫ. C реализацией exponential moving average возиться пока не стал ибо не уверен что есть смысл.
сказка о коммите
Tuesday, 27 December 2011 12:46Однажды тимлид собрал митинг и говорит: «А что, девелоперы, не деплоил ли из вас кто-нибудь на продакшен?» Все сказали: «Нет». Джуниор Ваня покраснел как рак, и сказал тоже: «Нет, я не деплоил».
Тогда тимлид сказал: «Что кто-нибудь из вас задеплоил прод и не признаётся, это нехорошо; но не в том беда. Беда в том, что в предпоследнем коммите есть недоделанный кусок, и если кто задеплоил его, то консистентность базы нарушится. Я этого боюсь».
Джуниор Ваня побледнел и сказал: «Нет, я тот коммит откатил».
И все засмеялись, а Ваня заплакал.
Тогда тимлид сказал: «Что кто-нибудь из вас задеплоил прод и не признаётся, это нехорошо; но не в том беда. Беда в том, что в предпоследнем коммите есть недоделанный кусок, и если кто задеплоил его, то консистентность базы нарушится. Я этого боюсь».
Джуниор Ваня побледнел и сказал: «Нет, я тот коммит откатил».
И все засмеялись, а Ваня заплакал.
(no subject)
Monday, 26 December 2011 21:01Похоже, в архитектуре я немного лажанулся.
Когда речь заходит о монотонно растущих каунтерах, можно в принципе реализовать дельты прямо в коллекторе, но производные будут получаться слишком "мгновенные".
Беда в том, что цикл сэмплирования меньше цикла обмена примерно в два раза (примерно.... не менее чем в два). Это так и задумано, по ряду причин, и это не проблема когда мы берём циферки как таковые. Но когда мы считаем дельту/время - с одной стороны, ловятся короткие пики, которые на бОльшем интервале усреднения смазались бы. Это бы хорошо, но, с другой стороны, дельты 60/0/60/0 на вдвое бОльшем интервале дали бы 30/30, а реально сервер может выхватить из них 0/0. Или вообще один сэмпл 60, тут вам не риалтайм и не очереди.
Переносить всю эту обработку в центр не хочется: и в транспорт непонятно как засовывать сырые данные (то есть понятно, но криво всё это получается), да и пределы конфигурируются на коллекторе - их тоже придётся передавать, потому что раскидывать конфигурацию по компонентам этосюда-этотуда-атутярыбузаворачивал будет совсем уж глупо.
Можно вот что, можно в коллекторе взвешенное скользящее среднее сделать. Так даже прикольнее получится.
Когда речь заходит о монотонно растущих каунтерах, можно в принципе реализовать дельты прямо в коллекторе, но производные будут получаться слишком "мгновенные".
Беда в том, что цикл сэмплирования меньше цикла обмена примерно в два раза (примерно.... не менее чем в два). Это так и задумано, по ряду причин, и это не проблема когда мы берём циферки как таковые. Но когда мы считаем дельту/время - с одной стороны, ловятся короткие пики, которые на бОльшем интервале усреднения смазались бы. Это бы хорошо, но, с другой стороны, дельты 60/0/60/0 на вдвое бОльшем интервале дали бы 30/30, а реально сервер может выхватить из них 0/0. Или вообще один сэмпл 60, тут вам не риалтайм и не очереди.
Переносить всю эту обработку в центр не хочется: и в транспорт непонятно как засовывать сырые данные (то есть понятно, но криво всё это получается), да и пределы конфигурируются на коллекторе - их тоже придётся передавать, потому что раскидывать конфигурацию по компонентам этосюда-этотуда-атутярыбузаворачивал будет совсем уж глупо.
Можно вот что, можно в коллекторе взвешенное скользящее среднее сделать. Так даже прикольнее получится.
(no subject)
Monday, 26 December 2011 17:24Сохранить и перечитать при случае
http://habrahabr.ru/blogs/python/114576/
http://habrahabr.ru/blogs/python/114585/
http://habrahabr.ru/blogs/python/114587/
http://habrahabr.ru/qa/4847/#answer_20947
Помню, в децтве книжка про смолтолк выносила мне мозги своими : Metaclass, абстрактный суперкласс всех метаклассов, сам является экземпляром класса class, который в свою очередь, является его подклассом :]
http://habrahabr.ru/blogs/python/114576/
http://habrahabr.ru/blogs/python/114585/
http://habrahabr.ru/blogs/python/114587/
http://habrahabr.ru/qa/4847/#answer_20947
Помню, в децтве книжка про смолтолк выносила мне мозги своими : Metaclass, абстрактный суперкласс всех метаклассов, сам является экземпляром класса class, который в свою очередь, является его подклассом :]
практикум по алгоритмизации
Monday, 26 December 2011 13:57Задача 2. Определить, является ли введённая строка палиндромом («перевёртышем») типа ABBA, kazak и пр.
Постановка задачи: Требуется сравнивать попарно символы с начала и с конца строки S (первый и последний, второй и предпоследний и т.д.). Если в каждой такой паре символы одинаковы, строка является палиндромом. Соответственно, каждая проверка пары символов должна получить некоторый признак ( flag — «флаг»), который будет равен 1, если символы в паре совпадают и 0, если не совпадают. Окончательный результат обработки строки получится как произведение всех значений «флагов». Если хотя бы один раз «флаг» оказался равен нулю, строка палиндромом не является и произведение всех «флагов» окажется равным 0. Количество пар не превышает половины длины строки L (точно равно половине длины для строк с чётным количеством символов и результат целочисленного деления длины строки на 2 для строк с нечётным количеством символов, поскольку «центральный» символ строки с нечётным количеством символов очевидно совпадает сам с собой).
[блаблабла, код на псевдоязыке, код на питоне]
Однако использование особенностей строк в Python, их функций и методов, позволяет решить эту задачу более изящно. Например, так.
# -*- coding: utf-8 -*-
#
s1=raw_input('Исходная строка:')
lst=list(s1)
lst.reverse ()
s2=' '. join(lst)
if s1==s2:
print 'Палиндром'
else:
print 'Не палиндром!'
Здесь исходная строка преобразуется в список, затем список «переворачивается» и из него с помощью пустой «строки-объединителя» формируется новая строка. Затем строки сравниваются. Цикл оказывается не нужен! Всю работу делает Python.
http://www.altlinux.ru/news/archive/2011/01/item/624/ [via http://www.linux.org.ru/news/doc/5866584]
Нечего сказать, изящненько, /me закрывает лицо рукой
Постановка задачи: Требуется сравнивать попарно символы с начала и с конца строки S (первый и последний, второй и предпоследний и т.д.). Если в каждой такой паре символы одинаковы, строка является палиндромом. Соответственно, каждая проверка пары символов должна получить некоторый признак ( flag — «флаг»), который будет равен 1, если символы в паре совпадают и 0, если не совпадают. Окончательный результат обработки строки получится как произведение всех значений «флагов». Если хотя бы один раз «флаг» оказался равен нулю, строка палиндромом не является и произведение всех «флагов» окажется равным 0. Количество пар не превышает половины длины строки L (точно равно половине длины для строк с чётным количеством символов и результат целочисленного деления длины строки на 2 для строк с нечётным количеством символов, поскольку «центральный» символ строки с нечётным количеством символов очевидно совпадает сам с собой).
[блаблабла, код на псевдоязыке, код на питоне]
Однако использование особенностей строк в Python, их функций и методов, позволяет решить эту задачу более изящно. Например, так.
# -*- coding: utf-8 -*-
#
s1=raw_input('Исходная строка:')
lst=list(s1)
lst.reverse ()
s2=' '. join(lst)
if s1==s2:
print 'Палиндром'
else:
print 'Не палиндром!'
Здесь исходная строка преобразуется в список, затем список «переворачивается» и из него с помощью пустой «строки-объединителя» формируется новая строка. Затем строки сравниваются. Цикл оказывается не нужен! Всю работу делает Python.
http://www.altlinux.ru/news/archive/2011/01/item/624/ [via http://www.linux.org.ru/news/doc/5866584]
Нечего сказать, изящненько, /me закрывает лицо рукой
(no subject)
Thursday, 1 December 2011 01:03Итоги подведём:
$ git log --stat --since 'Nov 30' | grep Date | tail -1
Date: Wed Nov 30 12:29:21 2011 +0200
$ git log --stat --since 'Nov 30' | grep Date | head -1
Date: Thu Dec 1 00:43:36 2011 +0200
$ git diff --stat $(git log --stat --since 'Nov 30' | grep commit | tail -1 | cut -d\ -f 2) Agent/ | tail -1
22 files changed, 357 insertions(+), 270 deletions(-)
Допилил два больших куска, вокруг/ради них немного порефакторил, ничего при этом не сломав, и оттестировал на двух платформах.
gns умный, gns теперь может взять баночку пива или даже две и вызвать такси. Чота заебался немножко.
$ git log --stat --since 'Nov 30' | grep Date | tail -1
Date: Wed Nov 30 12:29:21 2011 +0200
$ git log --stat --since 'Nov 30' | grep Date | head -1
Date: Thu Dec 1 00:43:36 2011 +0200
$ git diff --stat $(git log --stat --since 'Nov 30' | grep commit | tail -1 | cut -d\ -f 2) Agent/ | tail -1
22 files changed, 357 insertions(+), 270 deletions(-)
Допилил два больших куска, вокруг/ради них немного порефакторил, ничего при этом не сломав, и оттестировал на двух платформах.
gns умный, gns теперь может взять баночку пива или даже две и вызвать такси. Чота заебался немножко.