Monday, February 1, 2016

Разработка через тестирование. Нужно ли это.

Я попробую тезисно ответить на вопросы

Нужно ли TDD?
Когда его стоит внедрять?
Замедляет ли работу написание тестов?
Какие подводные камни вы можете встретить?

Нужно ли TDD. 


Очень трудно ответить на этот вопрос, для начала нужно внести ясность. 

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

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

Еще сложный технический момент, само по себе написание тестов не такая простая задача, если вы учитесь писать тесты, то по сути вы учитесь заново программировать ( при условии что вы все делаете правильно) 

Или например вы пишите тесты, но высокого уровня. Например тесты на views для django. Или любые другие высокоуровневые или приемочные тесты  и только их. Это уже не TDD. Вы покрываете систему требованиями, но не контролируете архитектуру. Это разработка через приемочные тесты. 

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

Ну и финальный ответ на поставленный вопрос - прост. Обладая опытом разработки через TDD уже после пары проектов вы начнете видеть нужно ли TDD для каждой конкретной задачи. Поэтому как "идеальный программист" вы должны освоить эту методологию. 

Когда его стоит внедрять?

Wednesday, January 27, 2016

Как стать веб разработчиком?

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

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

 Так как же стать веб разработчиком? 

Tuesday, January 12, 2016

Ссылка на статью с объяснением statsd, graphite, и на объяснение upper_90, mean_90 метрик

Долго не понимал как точно все это работает. Спасибо коллеге дал мне хорошую ссылку.

https://blog.pkhamre.com/understanding-statsd-and-graphite/

Вдогонку, понятное объяснения метрик upper_90 и т.п.

https://gist.github.com/ageron/5212412

Sunday, December 6, 2015

Docker для разработчика. Основы. Часть 1.

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

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

До докера моя рабочая машина включала в себя.

  1. mysql
  2. memcache
  3. redis
  4. elasticsearch
  5. rabbitmq
  6. statsd
  7. logstash
  8. postgresql
  9. mongo
  10. python 2.7
  11. python 3.4
  12. python 3.5
  13. nginx
  14. apache
  15. phing
  16. ant
  17. fabric
  18. Порядочное число библиотек, для сборки которых нужны была уйма *dev пакетов. 
  19. И наверное еще кучу мелочей которые сходу и не припомнить. 
Отдельной песней шел один из проектов который состоит из нескольких сервисов, настройка проекта в целом занимала очень долгое время. Помню когда мы наняли первого стороннего разработчика, настройка его рабочего места заняла практически день. Теперь, новый человек, который приходит в проект получает рабочее место в пределах часа.  В качестве дополнительного бонуса, все работы для быстрого разворачивания пригодились в CI сервере. 

Итак что за софт у меня теперь? 
  1. docker
  2. docker-compose
  3. ant который идет в idea, просто для удобства
У меня теперь нет даже php, несмотря на то, что я веду несколько проектов на нем. У меня нет python3.4, python3.5, хотя все остальные проекты на нем. И даже python 2.7 который есть в системе я не трогаю для своих целей. 

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

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

Теория


Итак основы.  Я не собираюсь подробно расписывать все возможности. Сосредоточусь на минимуме, которого хватит для его использования при разработке.
  1. docker - это демон который позволяет запускать из образов (images) процессы (контейнеры, containers)
  2. image - образ для запуска процессов ( контейнеров). Состоит из набора файловых систем, которые наслаиваются друг на друга, образуя единую файловую систему. Слои обычно выглядят как
    1. Операционная система.
    2. Библиотеки
    3. Программа. 
  3. container - процесс запущенный на основе образа. По умолчанию все запущенные контейнеры живут в отдельной подсети созданной демоном докера. 

Программисту, возможно, будет проще воспринимать image как класс, а container как объект созданный из класса (образа).  Отсюда все можно делать простые выводы, два контейнера запущенные из одного образа - независимы, но при запуске имеют одинаковую файловую систему.

Важно! docker image может базироваться на любой операционной системе, никто вам не помешает запускать docker image memcached на основе debian stable из вашей операционной системы, пусть у вас будет ubuntu или slackware. memcached запущенный внутри докера будет считать что он работает из под debian. 

Основные команды. 

1. Запуск контейнера, например нам нужен memcached 1.4  для работы. (!!!! минимум 132 Мб будет скачено)

docker run  -d memcached:1.4
82a90b065744f43fa2efd8f00fece0faeacc53c7d306cc54b137482728d8c9bb
-d Говорит докеру чтобы он запустил новый контейнер в фоновом режиме, без этой опции контейнер будет запущен, а ваш терминал будет получать stdout/stderrs от запущенного контейнера.

Мы запустили контейнер с memcached сервисом, из образа memcache версии 1.4 который докер будет искать в публичном репозитории докера. Если образа не было в системе, он скачается. 

2. Скачать образ 

docker pull memcached:1.4

Команда
docker pull memcached 
Эквивалентна
docker pull memcached:latest 
Т.к. latest это версия используемая по умолчанию. 

3. Список запущенных контейнеров

docker ps 

CONTAINER ID        IMAGE                                   COMMAND  ....
82a90b065744        memcached:1.4                           "memcached"   ...

Важно! 82a90b065744 это ID созданного контейнера, у вас будет другой. И во все команды нужно будет подставлять именно ваш. 


4. Узнать подробности о запущенном контейнере 

docker inspect 82a90b065744 | less

В частности нас интересует  "IPAddress": "172.17.0.89". (У вас будет другой)

Также ip запущенного контейнера можно получить при помощи команды 
docker inspect --format '{{ .NetworkSettings.IPAddress }}' 82a90b065744
Проверяем этот адрес 

cd:$ telnet 172.17.0.89 11211
Trying 172.17.0.89...
Connected to 172.17.0.89.
Escape character is '^]'.
set key 0 900 4
data
STORED
get key
VALUE key 0 4
data
END
quit
Connection closed by foreign host.


Видим что memcached запущен и работает. 

5. Остановка контейнера

cd:$ docker stop 82a90b065744 
82a90b065744

6. Повторный запуск остановленного контейнера 
cd:$ docker start 82a90b065744
82a90b065744

7. Попадаем внутрь контейнера

cd:$ docker exec -it 82a90b065744 /bin/bash
memcache@82a90b065744:/$ ls
bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

-i - интерактивный режим.
-t - прикрепить терминал 
/bin/bash команда внутри докера которую вы хотите запустить

8. Удаление контейнера (предварительно его нужно остановить)

cd:$ docker rm  82a90b065744
82a90b065744

Где искать образы? 

https://hub.docker.com/ отдавайте предпочтение официальным образам, либо образам крупных компаний, как правило они правильнее собраны и имеют меньший размер. 

Теперь мы умеем. 

1. Находить нужные образы
2. Скачивать нужные образы
3. Запускать контейнеры из образов
4. Попадать внутрь запущенного контейнера.
5. Останавливать контейнер


Практика:


Что нам дают эти знания?

1. У вас есть проект, который использует mysql, локально у вас установлен mysql5.3, ошибка воспроизводится на mysql 5.4.

Делаете дамп базы.
mysqldump mydatabasename -u myuser -pmypassword > projectname.dump.sql
Запускаете mysql5.4. из образа

docker run  -e MYSQL_ROOT_PASSWORD=root mysql:5.4

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

-e ENV_NAME=env_value для команды run - создает переменную окружения ENV_NAME с указаным значением для запущенного контейнера.

Заливка дампа во вновь созданный контейнер:

находим id образа
docker ps
получаем его ip 
docker inspect --format '{{ .NetworkSettings.IPAddress }}' IDOFMYSQLCONTAINER
Внутри контейнера создаем базу данных, заливаем дамп, меняем настройки доступа - исправляем ошибку.

Уже одного этого более чем достаточно для того чтобы начать использовать docker : )

Какие еще преимущества нам дает? Пусть вы используете gentoo и mysql 5.3.  Ошибка воспроизводится на ubuntu 14.04 и mysql 5.3. Вы ищете образ сделанный на основе ubuntu 14.04 с установленным mysql5.3 запускаете контейнер и получаете mysql 5.3. работающий внутри ubuntu 14.04 на вашем ненаглядном gentoo.


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





Saturday, November 28, 2015

Тренировки программиста, ката. Задачки. Обучение.

Прежде чем писать, есть желание заинтересовать. Простая задачка с codewars.com для затравки.
Дана последовательность четных и нечетных чисел, в которой только одно четное число или только одно нечетное, нужно вернуть позицию того единственного числа. Нумерация начинается с 1. 
iq_test("2 4 7 8 10") => 3 // Third number is odd, while the rest of the numbers are even 
iq_test("1 2 1 1") =>  2 // Second number is even, while the rest of the numbers are odd

Ответы - в спойлере. Открыть спойлер


Почему я начал решать такие задачи?

Недавно заметил что работа для меня превратилась в рутину. Интересных задач как и маленьких открытий  остается все меньше и меньше. Я решил скомпенсировать такой ход дел поиском сайтов, помогающих в развитии навыков программирования. Сайты в стиле - напиши AI и прочее я отмел сразу. Но открыл для себя: 

  1. http://www.codewars.com/ - каты программирования.
  2. https://www.codeeval.com/ - интересные задачки.
Честно говоря решив пару тривиальных вещей на codewars  я решил к этому сайту не возвращаться. Но буквально же в этот день прочитал книгу "Идеальный программист. Как стать профессионалом разработки ПО" Роберта Мартина. Особо отметил некие "ката" смысл которых не понял. Действительно, мне показалось глупым раз за разом писать один и тот же алгоритм "совершенствуя" свои навыки. В результате в качестве исследования я решил позаниматься неделю на codewars, результаты ниже. А в чем же смысл ката по Мартину? 

Обычно при кодировании мы придумываем решение и затем его реализуем. Иногда решение придумано либо неправильно либо не продумано до конца. В этот момент мы кидаемся с одного на другое. Если например мы будем знать решение задачи и более того мы его уже несколько раз реализовывали то что нам это дает?

  1. Наработка механической памяти между задачей и решением.
  2. Занесение в подкорку типовых приемов 
  3. Если механика набора уже наработана, то мы можем осваивать IDE изучая новые комбинации хоткеев, которые позволят нам сократить время набора кода. 
  4. Реализация известного решения на новом языке позволит его быстрее изучить. 
В целом codewars.com немножко не про то. Но вот прошла неделя когда я каждый день делал по одной ката в день на двух языках один из которых даже не знаю. И теперь могу поделиться результатом. 


Сама реализация ката занимает очень короткое время. Т.е. тут нет задачи где стоит подумать. Больше упирается в то, насколько лаконично ты реализуешь задачу. После ее решения можно сравнить свой результат с другими людьми, а это уже интересно. 

  1. Я достаточно неплохо знаю питон, но тем не менее мой результат от лучших зачастую был чуть длинее чем лучший, при этом на него похож. Почти из каждой задачи я вынес "best practice" которые уже использую в работе. 
  2. В es6 я не силен, поэтому вначале очень много подчерпнул из особенностей языка особенно на первых порах. Сильно помогало, что эту же задачу до этого я решал pyton поэтому сосредотачивался уже на нюансах языка
  3. На этом сайте ужасные js программисты, сравнивая решения python и js хочется развидеть все решения на js :) Хотя конечно есть и хорошие.
  4. Мои "наработанные" приемы уменьшения объемы кода или его украшательства иногда давали решения длиннее чем грамотно использованные if :) 

Ну и самое главное. Я действительно стал набирать код более качественно. В целом набор кода у меня занимает очень мало времени, но периодически возникали огрехи - не там закрыта скобка, не там поставлена точка, забыта запятая. После недели занятий  я стал допускать в два раза меньше таких огрехов. 

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

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

Также на codeeval ценна оптимизация. Если на codewars часто видно не оптимальные решения, но короткие, то на codeeval наоборот :) Поэтому зачастую получив низкую оценку за задачу, хочется выполнить ее быстрее. 

Ну и на codeeval нельзя (или я не знаю как ) посмотреть результаты других участников. 

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


Tuesday, November 13, 2012

Индикатор загрузки данных

Мелочь а вечно не мог подобрать подходящий. Вот: (ajaxload.info) замечательный сервис для генерации разнообразных индикаторов.

Friday, July 6, 2012

yii + startup, история запуска и оптимизации

Некоторое время назад сработал проект выпущенный группой единомышленников с моим участием. (30к уников в день/200к страниц в день)

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

1. В команде не было дизайнера/верстальщика, мы готовы были в целом их оплатить, но идея висела в воздухе, нужно было реализовать все крайне быстро.

В этом нам очень сильно помог twitter bootstrap framework, а также решение об использовании yii bootstrap extension. Не обладая внятными навыками в разработке интерфейсов мы смогли сделать достаточно простое приложение.

2. Ожидался минимальный функционал. Нужно было быстро разработать, а также иметь возможность развернуть на любом хостинге, сервера у нас не было.

Использование php + yii framework дало нам возможность реализовать прототип в предельно сжатые сроки. Это было действительно быстро.

3. Одна из основных фишек проекта, поиск по документам по ряду аттрибутов.

mysql + избыточность хранения данных, дали нам предельно быстрые выборки для небольших объемов данных. На текущем этапе смотрим в mongodb, теоретически это дает нам идеальный результат. Дело в том что аттрибуты для сущностей получаются в виде json, мы можем хранить готовые документы в mongodb в оригинальном виде, что заодно и решит проблему с версионностью документов.

4. Хранение файлов и место. Это одна из основных проблем, основная проблема в отстуствии прогнозируемых трат при использовании облачных хралищ данных.

Тут решения все еще нет :( Пока обходимся арендой сервера с большим дисковым пространством.

Все эти проблемы были встречены при разработке и особых проблем не вызвали. Самое веселое началось при росте посетителей.

5. Монетизация, хоть это и известно, но было неприятно понять на своей шкуре, что о монетизации нужно было заботиться заранее.

Пожертвования совсем себя не оправдали.

Баннеры кое как покрывали расходы на первый сервер. Эффективность крайне низкая.
Инвестиции от заинтересованных в развитии проекта лиц спасли его. Т.е. в общем случае мы могли вылететь в трубу.

1. База данных как всегда начала дохнуть первой.

При помощи memcached избавились от yii запросов к кэшированию схем таблиц, полностью были закешированы запросы к словарям ( они у нас практически не меняются). Это снизило нагрузку в два раза.

Далее шел вдумчиваый анализ каждой страницы, и рутинная работа по оптимизации запросов, изучения возможности их кэширования. После этого за базу данных мы были спокойны.

2. После того как разобрались с базой, имели большой load average и нагрузку на диски, думали это из за специфики сервиса, оказалось все до тривиального просто. Одно из расширений было включено в режиме дебага, а в этом режиме оно перезаписывало кучу assets'ов при каждом обращении к страницы. Это создавало дикую нагрузку на диск. Собственно запретив для него debug мы полностью решили свои проблемы.

3. У нас был мониторинг медленных запросов, он начал пухнуть, причем на операции которая по cron script обрабатывала документы, для построения ряда специфических поисковых таблиц. И эти операции лочили таблицы. Вместо того чтобы запускать его с определенной периодичностью отдавая небольшую пачку документов, мы перенесли его на время, когда пользователей на сайте практически нет, и заставили его обрабатывать все документы за день. Лог медленных запросов стал практически пустым. 4. Теперь у нас быстро генерировалась страница, но долго грузилась в браузере. Использование scriptMap + yiicomporessor решели проблему кучи js скриптов, а также их минимизации.

Использование yii extension contentCompresssor позволило ужать наш гигантский html до внятных размеров.

За счет этих простых триков, по версии google analytics мы ускорили время загрузки страницы с 5 секунд до 1й секунды.

Не знаю имеет ли смысл в этой статье без особых тех подробностей, но показал ход развития проекта и общие принятые решения. Мне было крайне интересно, а ведь будет еще интереснее :)

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

Monday, March 26, 2012

Мелочи для mysql консоли

Век живи век учись.

Поработав с postgres, понравилось отображение текущей базы данных в строке состояния консоли.

Вернувшись к mysql мне стало этого сильно не хватать. Но оказывается это делается очень просто.

В консоли ( или .bashrc ) устанавливаем нужную строку состояния, например

#export MYSQL_PS1="(\u@\h) [\d]> "

Запускаем клиента
#mysql

Получаем строку состояния вместо

mysql>

Вот такую

(USER@HOSTNAME) [DBNAME]>




ТАкже из консоли mysql можно узнать имя текущей базы при помощи запроса

mysq>select database();

Wednesday, August 24, 2011

Крик души, мнение о Zend Framework.

Меня тошнит от Zend Framework. Вот получили задание, переписать существующий сайт на Zend Framework. Задача не сложная, текущий код "портяночный", неправильный, там нет разделения логики от вида и т.д. Но вот я смотрю его, он простой, легко читаемый, прозрачный. Видно что его писал неглупый, возможно неопытный человек. Но в нем присутствует стройность. Более того, я считаю, что код вполне соответствует задаче.

И вот мы начинаем его переписывать. В своей жизни я создал порядка 5ти сайтов на ZF. Начинал еще с версии 0.6, потом 0.9, далее не помню. Тем не менее считаю что сам фреймворк не знаю абсолютно.

И вот как я представлю в какое умно все это превратится. И ведь даже выразить это невозможно.

Я уважаю Zend Framework как набор компонент, они удобны, хороши, просты, с ними мало проблем. И я удивляюсь, насколько убого создано то, что является MVC Framework'ом на основе этих компонент.

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

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

Так сука и хочется спросить. Если вы такие умные хуле вы на пхп пишете?

Казалось бы нытье? Наверное да. Но вот открываю я quickstart zf. А конкретно http://framework.zend.com/manual/en/learning.quickstart.create-model.html вот этот раздел.

Нас в мануале учат что для работы с базой мы будем использовать DataMapper, который нам вернет модели, которые мы assign в темлейте и будем там использовать. Т.е. получается.

1. наследни DbTable
2. Кастомный DataMapper
3. Кастомная модель??? Структура с кучей геттеров.

Что я вижу?

1. Классы не требуются, т.е. это идет на уровне соглашения. Т.е. в мануале нам говорят как писать, но это не требуется, более того применения не будет.
2. Весь такой клевый гибкий ооп зенд, в своем мануале нам показывает как в DataMapper ОБЪЕКТ!! превращают в сырой массив (словарь,хэш) который передается дальше! Вообще в этом так то плохого ничего нет. Но как это делается!!!
 
<?php

foreach ($resultSet as $row) {
$entry = new Application_Model_Guestbook();
$entry->setId($row->id)
->setEmail($row->email)
->setComment($row->comment)
->setCreated($row->created);
$entries[] = $entry;

Что меня тут убивает. Вообще row я как понимаю инстанс Zend_Db_Tale_Row у котого есть метод toArray()

Application_Model_GuestBook в конструкторе может принимать массив значений.

т.е. написать $entries[] = new Application_Model_Guestbook( $row->toArray() ) ; Видимо религия не позволяет.

Ну конечно они возможно хотели показать как должны идти преобразования данных в том случае когда хранение данных отличается от доменной модели. Но ведь нет таких объяснений. И пример крайне неудачный. Даже далеко не у самых глупых программистов должен возникнуть простой вопрос. А на хрена?

Насколько пользователей нужно считать идиотами я даже не знаю. Конечно можно возразить что это мануал, а умный напишет как правильно, да блин 60% клинических идиотов потом так и будут писать. Вот в текущей задаче есть таблица на 40 полей. Конечно скорее всего база данных будет изменена, но блин вот напишут мне модельку из 40 геттеров, 40 сеттеров, 40 филдов. А потом внутри датамаппера 40 строчек сета, вот красота то будет. А потом ищи опечатку среди этого бардака.

Ну ладно так то мелочь и можно забыть, таки мануал и я 100% уверен что люди так не пишут. НО!!!

http://habrahabr.ru/blogs/zend_framework/123285/

Финиш. Все. Приплыли. Люди так пишут! Правда они умные, они блять парсят sql. И генерят код чтобы не опечататься.

У них ведь даже базовые классы появились :(

Бли до какой степени все убого, даже углубляться не хочется :( Как я люблю yii framework.

Ну вот получите
 

class I_Love_Zend_Naming_Huh_Model
{
protected $_properties = array();

protected $_data = array();

public function __construct(array $options = null )
{
if ($options !== null )
$this->fromArray($options);
}

public function __set($name, $value)
{
$this->__checkProperty($name);//check if property exists

$setterMethod = 'set'.$name;//buid setterName

if (method_exists($this, $setterMethod))// if setter exists
return $this->$setterMethod($value);//call it

$this->_data[$name] = $value;//no setter, remember data
return $this;
}

public function __get($name)
{
$getterMethod = 'get'.$name;

if (method_exists($this, $getterMethod))
return $this->$getterMethod();

if (key_exists($name, $this->_data))
return $this->_data[$name];

return null;
}

public function toArray()
{
return $this->_data;
}

public function fromArray(array $options)
{
foreach ($options as $key => $value)
$this->$key = $value;

return $this;
}

private function __checkProperty($name)
{
if (!in_array($name, $this->_properties))
throw new Exception('Undefined property ' . $name );
}

}


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

class Super_Puper_Model extends I_Love_Zend_Naming_Huh_Model
{
protected $_properties = array('id', 'first_name', 'last_name');
}



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

Да на те

 

class Super_Puper_Model extends I_Love_Zend_Naming_Huh_Model
{
protected $_properties = array('id', 'first_name', 'last_name');

public function getId()
{
return $this->first_name . $this->last_name;
}
}

Все, можете не париться, ваш код как вызывал $entity->id, так и вызывает, а поведение поменялось.

А мне в ответ: "Ага! У тебя волшебные методы, все пиздец приплыли тормоза!" А в мануале их бля нет....

Я еще про могучие плагины не рассказал, а инверсия зависимостей ага, работает. Хрен найдешь что вызывается на самом деле. Код нельзя прочитать! (Ну конечно можно, особенно с опытом) Но вот на yii я написал 3 сайта, и считаю что его уже знаю. А на zf 5 и считаю что его не знаю :(

Я не спорю что он крутой. Но такой некрасиый, такой нестройный. Такой не логичный. В yii framework достаточно понять смысл CComponent класс вроде из (40-100) строк, и дальше уже все понятно. А тут :(.

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

Tuesday, March 29, 2011

yii, облегчаем прототипирование.

Введение



Я давно работаю с yii framework. И пока это единственный framework в мире php. Из минусов для меня то, что он слишком низкоуровневый, отсутствует много плюшек, которые я с успехом возмещаю разумным использованием Zend компонент. Как набор компонент Zend хорош, чего не скажешь о его framework'е.

Ну так вот, возвращаясь к низкоуровневому yii. Его CRUD хорош, хорош для прототипирования, но плодит кучу файлов. Многие из которых со временем вырезаются и сильно модифицируются. Моим кошмаром была работа на небольшим по объему backend проектом. Где вся работа сводилась к администрированию кучи таблиц с текстовыми и буленовскими полями. При помощи CRUD все было сделано быстро, но большую часть времени я потратил на украшение админки, checkboxes вместо input, фильтры вида true|false вместо 0,1 и тому подобной ерунды. Таблиц было много, view еще больше. Я с нежностью вспоминал django и жалел что проект на PHP.



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



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

Это получилось, пока начало, но проект не закончен. Но уже можно пользоваться.

Что получилось? Получился модуль который позволяет не запуская CRUD получить CRUD странички над любой моделью при условии что она отнаследована от особого ActiveRecord + в ней определен один единственный метод. В качестве небольшого бонуса есть простая возможность слегка менять список полей и их порядок на полученных страницах.


Ниже небольшой туториал как заставить это работать. Текста много, но он расчитан на новичка в yii. Если вы накоротке с yii можете сразу читать с установки модуля.



Инициализация yii проекта


Можно пропустить, если хотите воспользоваться существующим.
Описание как создать yii проект с нуля. Для того чтобы потом использовать модуль.


Создание директории проекта.



Я буду использовать локальный вебсервер, с дефолтными настройками. Проект создам в папке AdminTest внутри моего document root /var/www/

 
claymore:$ mkdir /var/www/AdminTest
claymore:$ cd /var/www/AdminTest/
claymore:$


Папка создана, скачиваем yii-framework
 
Среда 2011-03-30 11:03 box:/var/www/AdminTest
claymore:$ wget http://yii.googlecode.com/files/yii-1.1.7.r3135.tar.gz
--2011-03-30 11:12:58-- http://yii.googlecode.com/files/yii-1.1.7.r3135.tar.gz
Преобразование адреса yii.googlecode.com... 74.125.87.82
Устанавливается соединение с yii.googlecode.com|74.125.87.82|:80... соединились.
Запрос HTTP послан, ожидание ответа... 200 OK
Длина: 2550331 (2,4M) [application/x-gzip]
Saving to: «yii-1.1.7.r3135.tar.gz»

100%[=============================================================================================================================>] 2 550 331 780K/s в 3,2s

2011-03-30 11:13:01 (780 KB/s) - «yii-1.1.7.r3135.tar.gz» saved [2550331/2550331]

Распаковываем

claymore:$ tar xf yii-1.1.7.r3135.tar.gz

Переименовываем, или делаем символическую ссылку( кому как удобнее), я переименовываю, т.к. работаю в рамках демонстрации
   
claymore:$ mv yii-1.1.7.r3135 yii

Удаляю скаченный архив

claymore:$ rm yii-1.1.7.r3135.tar.gz

Проверяем что у нас есть распаковааная папка с yii framework

claymore:$ ls
yii

Переходим в папку со скриптами yii framework
 
claymore:$ cd yii/framework/

Запускаем скрипт создания проекта

claymore:$ ./yiic webapp /var/www/AdminTest/
Create a Web application under '/var/www/AdminTest'? [Yes|No] Yes
mkdir /var/www/AdminTest/css
generate css/form.css
generate css/main.css
generate css/bg.gif
generate css/ie.css
generate css/screen.css
generate css/print.css
mkdir /var/www/AdminTest/assets
mkdir /var/www/AdminTest/images
generate index.php
generate index-test.php
mkdir /var/www/AdminTest/themes
mkdir /var/www/AdminTest/themes/classic
mkdir /var/www/AdminTest/themes/classic/views
mkdir /var/www/AdminTest/themes/classic/views/site
mkdir /var/www/AdminTest/themes/classic/views/layouts
generate themes/classic/views/.htaccess
mkdir /var/www/AdminTest/themes/classic/views/system
mkdir /var/www/AdminTest/protected
mkdir /var/www/AdminTest/protected/data
generate protected/data/schema.sqlite.sql
generate protected/data/testdrive.db
generate protected/data/schema.mysql.sql
generate protected/yiic
mkdir /var/www/AdminTest/protected/messages
mkdir /var/www/AdminTest/protected/tests
mkdir /var/www/AdminTest/protected/tests/unit
mkdir /var/www/AdminTest/protected/tests/functional
generate protected/tests/functional/SiteTest.php
generate protected/tests/phpunit.xml
generate protected/tests/bootstrap.php
generate protected/tests/WebTestCase.php
mkdir /var/www/AdminTest/protected/tests/fixtures
mkdir /var/www/AdminTest/protected/tests/report
mkdir /var/www/AdminTest/protected/views
mkdir /var/www/AdminTest/protected/views/site
mkdir /var/www/AdminTest/protected/views/site/pages
generate protected/views/site/pages/about.php
generate protected/views/site/login.php
generate protected/views/site/index.php
generate protected/views/site/contact.php
generate protected/views/site/error.php
mkdir /var/www/AdminTest/protected/views/layouts
generate protected/views/layouts/column1.php
generate protected/views/layouts/column2.php
generate protected/views/layouts/main.php
mkdir /var/www/AdminTest/protected/runtime
generate protected/.htaccess
mkdir /var/www/AdminTest/protected/components
generate protected/components/Controller.php
generate protected/components/UserIdentity.php
mkdir /var/www/AdminTest/protected/config
generate protected/config/main.php
generate protected/config/test.php
generate protected/config/console.php
mkdir /var/www/AdminTest/protected/commands
mkdir /var/www/AdminTest/protected/commands/shell
mkdir /var/www/AdminTest/protected/controllers
generate protected/controllers/SiteController.php
mkdir /var/www/AdminTest/protected/models
generate protected/models/ContactForm.php
generate protected/models/LoginForm.php
mkdir /var/www/AdminTest/protected/extensions
generate protected/yiic.bat
generate protected/yiic.php
mkdir /var/www/AdminTest/protected/migrations

Your application has been created successfully under /var/www/AdminTest.
Среда 2011-03-30 11:03 box:/var/www/AdminTest/yii/framework
claymore:$

Проверяем
 
claymore:$ wget http://127.0.0.1/AdminTest/index.php -q -O - | grep Yii
....
Congratulations! You have successfully created your Yii application.
...

Создаем базу данных admintest. При помощи вашего любимого средства.
 
mysql> create database admintest;
Query OK, 1 row affected (0.02 sec)


Прописываем настройки базы данных в protected/config/main.php


Теперь у нас есть болванка для веб сайта на основе шаблона yii.



Установка модуля.



Переходим в директорию расширений. (Если пропустили первый шаг, замените /var/www/AdminTest/ на путь до папки вашего проекта, содержащей protected)


 
claymore:$ cd /var/www/AdminTest/protected/extensions/

Скачиваем расширение.


 
claymore:$ hg clone -q https://yii-rextensions.googlecode.com/hg/ YiisyCrudAdmin -r YiisyCrudAdmin

[Опционально] удаляем информацию о репозитории, особенно ваш проект уже под системой контроля версий.


 
claymore:$ rm -rf YiisyCrudAdmin/.hg
Среда 2011-03-30 11:03 box:/var/www/AdminTest/protected/extensions

Модуль установлен.



Пример создания crud административной страницы.


Для примера нам понадобится любая модель.


Создаем таблицу в созданной базе данных.


  
mysql> create table example(id int primary key auto_increment, name char(20), is_good_record tinyint(1) default 0 , long_description text);
Query OK, 0 rows affected (0.08 sec)

Переходим в папку protected


  
claymore:$ cd /var/www/AdminTest/protected

Создаем модель.


  
claymore:$ ./yiic shell config/main.php
Yii Interactive Tool v1.1 (based on Yii v1.1.7)
Please type 'help' for help. Type 'exit' to quit.
>> model Example example
generate models/Example.php
generate fixtures/example.php
generate unit/ExampleTest.php

The following model classes are successfully generated:
Example

If you have a 'db' database connection, you can test these models now with:
$model=Example::model()->find();
print_r($model);
>>

Модифицируем модель.


Цель. Отнаследоваться от расширения CActiveRecord объявленному в YiisyCrudAdmin.RActiveRecord. И реализовать абстрактный метод getFieldsDescription


Открываем /var/www/AdminTest/protected/models/Example.php в любимом редакторе


   
Yii::import("ext.YiisyCrudAdmin.RActiveRecord"); //Добавляем эту строчку импорта в верх файла.

В модель Example добавляем новый метод


   
Yii::import("ext.YiisyCrudAdmin.RActiveRecord");
class Example extends RActiveRecord
{
//...
// в последней версии данный метод не требуется в модели, может создаваться по желанию в классе ExampleAdmin
public function getFieldsDescription()
{
return array(
'is_good_record' => 'RDbBoolean',
'long_description' => 'RDbText',
);
}
//...
}

Этот метод предоствляет расширенную информацию о полях модели. На данный момент реализованы пока Bool и Text.


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


Создаем в вашем любимом редакторе controllers/ExampleController.php


    
Yii::import('ext.YiisyCrudAdmin.RController');
class ExampleController extends RController
{
protected $_modelName = 'Example';
}

Тестируем


Открываем в браузере http://127.0.0.1/AdminTest/index.php?r=example/admin


Получаем типичный yii crud с небольшими улучшениями.



  • Поля, отмеченные как boolean, отображаются в виде чекбоксов при редактировании. Также в наличии встроенный фильтр для этих полей на странице actionAdmin.

  • Текстовые поля при редактировании редактируются в textarea. При просмотре в griidview обрезаются. (Пока и в view обрезаются, но это уже решаемый вопрос :) )


Из плюсов такого подхода, все сделано стандартными средствами yii. Т.е. после этапа прототипирования, вы уже можете использовать существующую базу кода, достаточно заглянуть в RController


Гибкость, например в методе getFieldsDescription модели, элеметом массива может быть массив, формирующий CGridColumn, в этом случае все обрабатывается в обычном для yii ключе.


Улучшаем внешний вид


Как поменять порядок полей в actionAdmin ?


Прямо в файле модели создаем класс


    
Yii::import('ext.YiisyCrudAdmin.RModelAdmin');
class ExampleAdmin extends RModelAdmin
{
public function getAdminFields()
{
return array(
'name', 'long_description', 'is_good_record', 'id'
);

}
}

После этого в actionAdmin наблюдаем поля в указанном порядке

.

Как исключить поле из отображения в actionAdmin ?


   
Yii::import('ext.YiisyCrudAdmin.RModelAdmin');
class ExampleAdmin extends RModelAdmin
{
public function getAdminExcludedFields()
{
return array(
'id',
);
}
}

И это все. Оба метода можно комбинировать.



Ну и вот интерфейс RModelAdmin, отвечающий за порядок и отображаемые поля


  
public function getViewExcludedFields();// список полей для исключения в actionView

public function getAdminExcludedFields();//список полей для исключения в actionAdmin

public function getViewFields();// список полей для отображения в actionView

public function getSearchFields();//список полей для отображения в расширенной форме поиска actionAdmin

public function getAdminFields();// список полей для отображения в actionAdmin

public function getFormFields();// список полей для создания/редактирования.


Почему методы а не массивы? Например для динамической генерации в зависимости от условий.




UPDATED:


В последней версии метод getFieldsDescription перенесен из модели в ModelAdmin класс.

Thursday, August 26, 2010

Немного об оптимизации веб-сайтов. часть 1.

Большую часть работы я трачу на поддержку и развитие уже написанных сайтов. Немалую долю времени при поддержке приходится тратить на оптимизацию. Именное ей и будет посвящен набор статей виде отдельных историй об одном абстрактном программисте.


Этап 1 - становление.


Опыт 1.


Начинающий веб программист получает заказ на небольшой сайт. На удивление сайт завершен за 2 недели до сроков, даже с учетом затребованных изменений. Программист понимает что код его далек от идеала, многие вещи реализованы не оптимально. Принято решение заняться оптимизацией сайта. Сказал - сделал. Время генерации сайта до оптимизации 0.02 секунды, время генерации сайта после оптимизации 0.015 секунды. Т.е. страница генерируется на 25% быстрее. Итак по мнению программиста сайт обслужит на 25% больше пользователей.


Проверка.


Пусть следующий скрипт эмулирует сайт до оптимизации.



$echo “<?php usleep(20000);” > /var/www/site_before.php

А следующий после:



$echo “<?php usleep(15000);?>” > /var/www/site_after.php

Запускаем проверку.



$ ab -q -n 1000 -c 150 http://127.0.0.1/site_before.php | grep "Requests per second"
Requests per second: 64, 3[#/sec] (mean) (усреднено по трем замерам)


$ ab -q -n 1000 -c 150 http://127.0.0.1/site_after.php | grep "Requests per second"
Requests per second: 64 [#/sec] (mean) (усреднено по трем замерам)

Результат.


Прироста в 25% нет. Есть падение производительности в рамках погрешности.


Предположение.


При фиксированных параметрах веб серверва и физических ресурсах сервера есть некое пороговое значение времени выполнения процесса ниже которого оптимизация теряет смысл.


Подтверждение



$echo “<?php ” > /var/www/site_null.php
$ ab -q -n 1000 -c 150 http://127.0.0.1/site_null.php | grep "Requests per second"
Requests per second: 67 [#/sec] (mean) (усреднено по трем замерам)

Т.е. даже если убрать весь код из нашего проекта мы получим лишь 4% прирост производительности вебсервера.


Объяснение.


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


Резюме.


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


Мое мнение.


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

Tuesday, June 22, 2010

Смена @author в eclipse|zend docblock

eclipse постоянно подставлял в phpdoc или скажем в pydoc напротив тэга @author имя текущего пользователя в системе, как я сильно от этого мучался. В итоге я потратил время на поиск решения и даже нашел:

- решение 1.

Запускать со следующими параметрами :

./eclipse -vmargs -Duser.name="User name"

- Решение 2.

Вручную заменив во всех шаблонах переменную ${user} на ваше имя.

Взято отсюда

Thursday, May 27, 2010

php type hinting

Навеяно http://habrahabr.ru/blogs/php/94714/


В статье обсуждаются предлагаемые в будущих реализациях механизмы проверки типов, в комментариях задевается текущая. А я хочу рассказать о том как я боролся с проверкой типов, в том числе и примитивных, в php средствами php.



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



Набросок использования предполагаемого решения для проверки типов данных на примере Yii framework. (В принципе от yii тут только component, что например в Zend может быть сделано на Registry, если там ничего не появилось нового, или на Toolkit в limb, ну или уж в крайнем случае singleton, глобальные переменные)



class AnyClass
{
/**
* anyFunction description
*
* @param integer $arg1
* @param string $arg2
*/
public function anyFunction($arg1, $arg2)
{
Yii::app()->typeHintingComponent->assertFunctionArgs($this,'anyFunction' get_funciton_args());
//orYii::app()->typeHintingComponent->assertFunctionArgs(__CLASS__,__METHOD__, get_funciton_args());
}


Ожидаемое поведение : в случае если тип $arg1 не integer или тип $arg2 не string или полученное количество аргументов не совпадает, мы получаем например RuntimeException.



Реализация :


  • По классу и имени метода получаем Reflection Method

  • У Reflection Method получаем docsting

  • Из dostring вытаскиваем типы данных

  • Проверяем

  • Делаем что душа пожелает, тут можно использовать либо стратегию (оно же вызывать call_back) , либо наследников с переопределением обработки ошибок, чтобы менять поведение.




В моем примере поведение меняется заданием component в конфиге.



В результате получаем отключаемую проверку типов, и можем управлять поведением.



Основным недостатком мне видится необходимость каждый раз явно указывать объект, метод, передавать полученные аргументы

Мое решение это написать функцию обертку что берет эти данные из debug_backtrace


//docstring
public function func($arg1, $arg2)
{
Yii::app()->typeHintingComponent->assertFunctionArgs();
}

//...
public function assertFunctionArgs()
{

$backTrace = debug_backtrace();
if ( ! isset($backTrace[1]['object'], $backTrace[1]['function'], $backtrace[1]['args']))
throw new RCAssertException(/* Details */);

$obj = $backTrace[1]['object'];
$function = $backTrace[1]['function'];
$args = $backTrace[1]['args'];
//далее по предыдущему алгоритму

}



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



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

Зато самое большое преимущество это возможность проверять даже не существующие типы

/**
* @param mixed $var ну и нафига проверка типов если используются такие переменные? :)
* @param string|integer $var2 тоже смешно, но на промежуточном этапе рефакторинга может пригодится
*/



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

Friday, March 26, 2010

Забавный php5.3

В debian testing неожиданно для меня появился 5.3, спустя час после этого "счастья", LAMP заработал как и раньше. Захотелось экспериментов, вот что в итоге получилось, на мой взгляд забавно для php.

<?php

class BaseObject extends stdClass
{
    public function 
__call($method$args = array())
    {
        return 
call_user_func_array($this->$method$args); //call closure
    
}
}

/**
 * Create className instance add getIncrementedAge, getName behaviours
 * 
 * @param string $className
 * @param integer $age
 * @param string $name
 * @return object same type as $className 
 */
function Type() 
{
    
    
$props func_get_args();
    
$className array_shift($props);//classname should be first 
    
    
$refFunction = new ReflectionFunction('Type');
    
$props array_combine(// make dictionary from function arg names (from phpdoc) and function arguments
        
array_map(//extract variable names from phpdoc @param line
            
function ($docString) { //search variable name and return it 
                
preg_match('/\$(\w+)/',$docString$data);
                return 
$data[1]; 
            },
            
array_filter//make lines array from phpdoc, filtering all except strings with @param and skip string with $className 
                
explode("\n"$refFunction->getDocComment()), 
                function (
$line
                { 
                        return 
strpos($line'param') > 0  && strpos($line'$className' ) < 1  
                } 
            ) 
        ), 
        
$props 
    
);
    
    
$ob = new $className(); 

    foreach ( 
$props as $name => $value
        
$ob->$name $value
        
    
$ob->getName = function () use($ob)  
    {
        return 
$ob->name
    };
    
    
$ob->getIncrementedAge = function ($inc =1) use($ob)  
    {
        return 
$ob->age $inc
    };
    
    return 
$ob;  
}

$cd Type('BaseObject'10,'cd');
$stas Type('BaseObject'20'stas');

var_dump($cd->getName());
var_dump($cd->getIncrementedAge(2));
var_dump($stas->getName());
var_dump($stas->getIncrementedAge(3));





Результат

php test.php
string(2) "cd"
int(12)
string(4) "stas"
int(23)

Monday, March 22, 2010

Так документировать код нельзя.

Сижу поздним ночером, хочу спать, нужно чуть изменить функционал модуля. Открываю, вижу

 
/**
* Returns the data model based on the primary key given in the GET variable.
* If the data model is not found, an HTTP exception will be raised.
* @param integer the primary key value. Defaults to null, meaning using the 'id' GET variable
*/
public function loadUser()
{
if($this->_model===null)
{
if(Yii::app()->user->id)
$this->_model=User::model()->findbyPk(Yii::app()->user->id);
if($this->_model===null)
$this->redirect(Yii::app()->controller->module->loginUrl);
}
return $this->_model;
}


Ну и как это вообще называется? Я бы убивал таких документаторов.

Где exception?
Где GET?
Где param int ?
Что return ?

Я уже промолчу про шедевральный

Yii::app()->controller->module->loginUrl

В корне поменявший поведение по умолчанию.


P.S.

Понял откуда это взялось, эту документацию и метод yii генерирует в контроллере при использовании crud команды. (За исключением @param int )

Sunday, August 16, 2009

Gtk-Message: Failed to load module "globalmenu-gnome"

Некогда пробовал gnome-globalmenu, не понравилось, убил.

С тех пор ловил при каждом запуске gtk приложения такую бяку:

Gtk-Message: Failed to load module "globalmenu-gnome": libglobalmenu-gnome.so: cannot open shared object file: No such file or directory
и


Решение тут:

http://magnus-k-karlsson.blogspot.com/2009/02/remove-gnome-globalmenu.html

Вкратце в gconf-editor /apps/gnome-settings-daemon/gtk-modules задизейблить ( убрать галочку) globalmenu-gnome

Wednesday, August 12, 2009

php и циклические ссылки

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

После беглого просмотра кода и сопутствущих классов, время было оптимизировано до минуты. Проблема была в том, что первоначальная версия была написана не очень хорошим программистом, а остальные ленились кардинально переписать приложение. Также для генерации отчета в циклах использовались классы, которые на многие действия совершали запросы к базе, а вызывались эти методы внутри цикла. Использовав упреждающее чтение всех необходимых данных в память и минимальную модификацию классов для использования получанных данных в случае их наличия, приложение стало работать гораздо быстрее (1 минута из которой 40 секунд происходит чтение всех необхомых данных в память, 20 секунд непосредственно обработка и запись данных). Ну а количество запросов сократилось до 3-х вместо 3*количество записей в обрабатываемых таблицах.

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

Вторая проблема которую я увидел это то, что это приложение было по сути копипастом из одного часто используемого метода, в итоге переписав и то, и другое, я получил скрипт, который исправно работал, но в 10 раз медленнее минутной версии, но обладал замечательным свойством, что был готов для повторного использования, стал более гибким, и читать его стало проще. Однако возникла следующая проблема, уже на 200.000 записях он стал отжирать 4 гб памяти и операционной системе это очень не нравилось, впрочем как и мне. Почистив код, я убедился что происходит утечка памяти.

Вот код, который вольно воспроизводит сложившуюся ситуацию.

 

class DataSource // в моем случае это был итератор по таблице базы данных
{
protected $_counter = 1;
public function next()
{
return array('xxxxxxxxxxxxxxxxxxxxxxxx' => 'yyyyyyyyyyyyyyyyyyy');
}
}

class UserHelper // род классов, для получения данных связаных с пользователями, но выходящих за рамки этого объекта
{
protected $_u = null;
public function __construct(User $u)
{
$this->_user = $u;
}
public function getSomeData()
{
return array('x' => 10, 'y' => 20);
}

public function __desctruct()
{
$this->_u = null;
}
}

class User
{
protected $_dbRow = array();
protected $_helpers = array();
public function __construct(array $data )
{
$this->_dbRow = $data;
}

public function getHelper()// получения хелпера, в данном случае ситуация урезана до одного
{
if ( isset($this->_helpers['x'] ) )
return $this->_helpers['x'];
return $this->_helpers['x'] = new UserHelper($this); // автор посчитал это полезным
}

public function __destruct()
{
unset($this->_dbRow);
$this->_helpers['x'] = null;
}
}

// кусок кода вызвавший проблему.
$ds = new DataSource();
for($i= 1; $i++; $i< 1000000)
{
$u = new User($ds->next()); // загружаем пользвоателя напрямую по данным
$helper = $u->getHelper();
$helperData = $helper->getSomeData();// получаем данные хелпера
//делаем то что нужно с данными
unset($helperData);
unset($helper);
unset($u); // думаем что убили пользователя
}


При запуске этого скрипта и запущенном топе, видим как память отжирается без остановки.

Проблема в циклических ссылках. Не охота это описывать можно почитать ТУТ

После того как я нашел утечку. Код переписан в таком виде


$ds = new DataSource(); // получаем итератор
for($i= 1; $i++; $i< 1000000)
{
$u = new User($ds->next()); // загружаем пользвоателя напрямую по данным
//$helper = $u->getHelper(); вот тут и была проблема.
$helper = new UserHelper($u);
$helperData = $helper->getSomeData();// получаем данные хелпера
//делаем то что нужно с данными
unset($helper);
unset($u); // думаем что убили пользователя.
}



После этого, время выполнения приложения стало чуть больше 20 % от первоначальной минуты, ну а раход памяти стал фиксированным ~300-400 MB

Вывод: Консольные приложения обычно пишутся для больших объемов данных, и то что прощается при выполнении маложивущей веб-страницы, выходит боком при долгой работе приложения.

Sunday, May 3, 2009

Неоднозначности PHP

За три месяца молчания накопились "прелести" PHP с которыми можно и поделиться.
Меньше слов больше консоли.

Для начала кусок докуметации о сравнении массивов


Для массивов $a и $b
$a == $b Равно TRUE в случае, если $a и $b содержат одни и те же элементы



php -a
php > $a = array(1,2);
php > $b = array(2,1);
php > var_dump($a == $b);
bool(false)


Для меня это оказалось несколько неожиданным изходя из документации, однако следущий код неожиданно вносит ясность.


php > class X {};
php > $x1 = new X();
php > $x2 = new X();
php > $a = array($x1, $x2);
php > $b = array($x2, $x1);
php > var_dump($a == $b);
bool(true)


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

Еще замечательный пример с которым я столкнулся в практике.

Во время рефакторинга, значение метода в случае ошибки (false) было изменено на (null) проверку понятное дело я забыл сделать, а по дефолту метод возвращал объект известного типа. В итоге код вида


php > $x = $object->getResult();
php > $x->value = 'smth';
php > $x->callSmth();

Возвращал Fatal error: Call to undefined method stdClass::callSmth() in php ... некоторое время я тупил удивляясь откуда взялся stdClass, (и сразу пошел в неверном направлении, греша на serialize при работе с memcache), однако быстро понял что дело не в этом.

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

php > $a = null
php > ;
php > $a->x = 10;
php > var_dump($a);
object(stdClass)#1 (1) {
["x"]=>
int(10)
}


Если провести тесты не с null, а скажем с boolean значением, получем тот же результат, по идее все логично, тогда код со строкой, должен отрабатывать эквивалентно.


php > $b = 'sdf';
php > $b->x = 10;

Warning: Attempt to assign property of non-object in php shell code on line 1


Все страньше и страньше. Ну и на последок забавный код.


php > class null{};#я думал так нельзя
php > class false extends null{};# можно и так
php > class true extends false { function __toString() { return 'false'; } } ; #это уже понятно
php > $false = 'false';
php > $true = new True();
php > var_dump($$true);#ну это уже стеб.
string(5) "false"


Велики возможности PHP !!!

Friday, February 20, 2009

Размышения о php, его применении

Статья о моих метаниях в процессе разработки и правильном использовании PHP.

Для затравки желательно прочитать статью 10 принципов PHP специалистов.

В принципе ничего нового. Теперь что думаю я.

1. PHP медленный язык. И не надо рассказывать о том, что большую часть времени занимает работа с базой, мемкэшем, файлами и т.д.
Даже вызов PHP фукнции достаточно дорогая операция. А уж подключения библиотеки - кошмар.

2. Фреймворки - монструозны, а если это сложить с пунктом один, получаем, что они еще и тормозны по определению.

Исходя из этих требования, а также под угрозами бешеных нагрузок я участвовал в написании проекта, который был минималистичен в плане порождения сущностей. На удобстве и скорости разработки это мало сказалось, т..к была изобретен минимальный фундамент (велосипед), который не давал скатиться в написание портянок. Зато время генерации при небольшой и средней нагрузках было 0.01-0.03 сек. Но т.к. администратор выбрал распределённую фс, это выразилось в подскакивании времени генерации до 0.1 сек.

В ходе этого я выяснил.

3. Проблемы описанные в пп 1,2 неприятны, но решаемы, в основном кэшированием и настройками серверов, или отказом от PHP.

Скажем так, оптимизация веб-сервера (отключение модулей апача, extensions php) дало прирост 10-20%, (смена апача на lighttpd +fcgi) дало прирост еще на 30%. Я бы не смог настолько оптимизировать код по всему проекту.

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

В итоге я ударяюсь (контролируемо) в другую крайность, в правильную разработку. Т.е. играемся с проектированием на всю катушку. Начинаем смотреть в сторону ОРМ уже в качестве необходимости.


Прошло время я наигрался и тем, и с другим. Сразу условлюсь что проект я предлагал делать на java, но руководство выбрало php по понятным всем причинам.

А теперь пример, который просто меня убил.

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

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

Реализация на php:

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


Реализация на python:

Twisted server, который в памяти хранит пользователей, отсортированных по результатам. При новых результатах, пользователь удаляется из отсортированного листа, ему начисляется новый скоринг, и он вставляется в список. Сам сервер по xmlrpc умеет отдавать результаты - веб серверу.
Веб сервер работает на старом движке, вместо запросов к базе - запросы к xmlrpc-server.

Реализация - быстрее чем на php(по времени разработки и отладки), скорость работы - выше, удобство использования - выше.

Самый большой плюс, система работает online!

Из недостатков - новая сложность системы в виде нового сервера.

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


Также могу привести еще несколько подобных вещей. В общем мое мнение ниша PHP - это все-таки fron-end, который отображает информацию от кучи разнородных сервисов, где собственно и будет реализована вся логика.
 
Каталог сайтов, Добавить сайт