Капец! :)))
Потихоньку подходит к концу конкурс 10K Apart. Заходишь, смотришь и думаешь - “дааа, чего только люди не впихнут в 10 килобайт JavaScript-а”. :)
Из наиболее понравившегося:
- Fontanero - увлекательная игрушка на тему “жизнь водопроводчика”. На Хабре про неё уже писали создатели.
- Poker Heads Up - долго играл, доиграл до конца, выиграл, понравилось. Из названия понятно, о чем игрушка. :)
- Racer 10k - вызвавшие ностальгию по моему первому компьютеру (а это был в далеком 1995-м году первый пентиум 133МГц) гоночки. Не очень затягивает, но не написать не мог. PS. Игра работает в Chrome намного быстрее, чем в FF, что положительным образом сказывается на результатах.
Уже давным давно мой отец просил меня сделать сайт модельному агентству его жены. Основная проблема заключалась в отсутствии дизайна сайтов и нежелании нанимать дизайнера (у меня с дизайном всё не очень хорошо, если не сказать хуже). В конце-концов я с горем-пополам нафигачил простенький дизайн и за пару дней приделал к нему базовых функционал и панельку для администрирования этого всего дела.
Комментарии по поводу улучшения как дизайна так и функционала категорически приветствуются. :)
Технические подробности: сайт использует Rails 3 RC, jQuery, MongoDB (в качестве ORM используется Mongoid), а для ресайза изображений используется CarrierWave (так как больше ничего поддерживающего Mongoid я не нашел) + RMagick + ImageMagick.
Кстати говоря, для того, чтобы запустить этот сайт мне пришлось обновить операционку на моем Linode-овском VPS, т.к. RMagick требовал более новую версию ImageMagick-а, чем та, которая была в репозиториях Ubuntu 9.04. Кстати, в Linode Library есть очень хороший гайд по обновлению.
Вот такую картину я увидел сегодня у себя в админке на Linode. Что-то в ней мне показалось странным. Через пару секунд я понял, что именно - надпись “Linode 512″, хотя я точно помнил, что у меня до этого было 360 мегабайт оперативки. Законнектился на сервак, проверил - действительно 360 мегабайт доступно. Зашел на страницу тарифных планов - действительно 360 поменялось на 512, да и остальные планы явно получили побольше оперативки. Как оказалось - увеличение оперативки произошло еще месяц назад, а я его не заметил, т.к. ни разу не перезагружал сервак с того момента, как он прекратил зависать, а для того, чтобы получить заветный прирост ресурсов - нужна перезагрузка.
Некоторые свои посты я дополняю постами-линками на Хабрахабре. Последний подобный пост - “Особенности работы jQuery.live()“. Он так и не попал на главную, но в данном случае это не очень важно, т.к. свой “хабраэффект” я поймал.
До этого у меня случались странности на сервере - он начинал есть все 400% процессора и наглухо зависал - к нему было невозможно подключиться и единственным вариантом восстановления был шатдаун инстанса в админке Linode. Это случалось нечасто и не особо меня парило. Но когда на мой сайт стали постоянно заходить люди с хабра - такое поведение сервера стало постоянным. Он начинал есть весь процессор. После перезагрузки он пару минут держался, а потом опять начинал свой бравое дело. Стало ясно - что-то явно не так в настройках.
После довольно недолгих разбирательств стало ясно, что виноват Apache (у меня стоит Apache + mod_prefork + php + ruby via passenger). Параметры prefork-а:
<IfModule mpm_prefork_module>
StartServers 5
MinSpareServers 5
MaxSpareServers 10
MaxClients 150
MaxRequestsPerChild 0
</IfModule>
Теперь мне стало очевидно всё, кроме одной вещи - почему же все-таки был загружен процессор… А причина зависаний сервера была проста - параметр MaxClients отвечает за то, сколько процессов апач может запустить для обработки пользователей (в prefork-е каждый запрос обрабатывается отдельным процессом, который либо запускается, либо вытаскивается из пула свободных запущенных процессов). Процессы апача ну очень недешевые в плане ресурсов - честно говоря, я до сих пор не знаю, как можно посмотреть, сколько реально кушает процесс апача - по текущей нагрузке на сервак при создании новых процессов апач кушает лишние 10-20мб памяти. 150 процессов * 10 мегабайт… Ну вы поняли. У меня сейчас обычныя VPS-ка на 360Мб. В общем, причина зависаний была в том, что апач создавал кучу процессов и уходит в своп по самое нехочу. А вот вернуться оттуда он уже сам не мог - ему нужна была помощь.
Выход - снижать количество максимально процессов апача. Методом научного тыка была выбрана цифра в 20 процессов. Собственно, сейчас эта цифра вполне себе хорошо работает. После уменьшения MaxClients и перезапуска сервера он падать перестал. Но появилась новая проблема - из-за хабраэффекта сервак хоть и не падал, но отдавал ответы очень долго. Эту проблему я решил не решать, т.к. в тот момент был на работе. Сейчас вот появилось немного времени и я решил протестировать свой блог на отзывчивость.
ab -c 1 -n 100 http://blog.fxposter.org/
Эмм… Результата я не дождался.
Для тех, кто не работал с ab и не знает, что это такое - Apache Benchmark.
ab -c 1 -n 10 http://blog.fxposter.org/
Результат меня неприятно удивил. Результаты, к сожалению, не сохранились, но могу сказать точно, что время генерации страницы было около 1.5 секунды (судя по тому, что сейчас показывает генерация страницы, если её нет в кеше).
Собственно, стало понятно, как бороть “хабраэффект” - нужно сильно ускорить отдачу странички. Как? Да очень просто - кешированием. Слава богу, что это WordPress и тут есть такие плагины, как wp-cache - прикручивается за несколько минут и пока что отлично работает. Если бы такого плагина не было, то, наверное, у меня бы была сейчас статья о том, что такое Reverse Proxy и как его использовать для кеширования. :) А так - поставил, включил. Результаты:
Заодно решил немного пооптимизировать клиентскую часть - добавить expires и сжатие контента (использовался модуль апача deflate).
Приведу конечные файлы:
# /etc/apache2/mods-available/expires.conf
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/gif "access plus 1 weeks"
ExpiresByType image/png "access plus 1 weeks"
ExpiresByType image/jpeg "access plus 1 weeks"
ExpiresByType text/css "access plus 1 weeks"
ExpiresByType application/javascript "access plus 1 weeks"
</IfModule>
# cat /etc/apache2/mods-available/deflate.conf
<IfModule mod_deflate.c>
# AddOutputFilterByType DEFLATE text/html text/plain text/xml
DeflateCompressionLevel 9
# Insert filter
SetOutputFilter DEFLATE
# Netscape 4.x has some problems...
BrowserMatch ^Mozilla/4 gzip-only-text/html
# Netscape 4.06-4.08 have some more problems
BrowserMatch ^Mozilla/4\.0[678] no-gzip
# MSIE masquerades as Netscape, but it is fine
# BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
# NOTE: Due to a bug in mod_setenvif up to Apache 2.0.48
# the above regex won't work. You can use the following
# workaround to get the desired effect:
BrowserMatch \bMSI[E] !no-gzip !gzip-only-text/html
# Don't compress images
SetEnvIfNoCase Request_URI \
\.(?:gif|jpe?g|png)$ no-gzip dont-vary
<IfModule mod_headers.c>
# Make sure proxies don't deliver the wrong content
Header append Vary User-Agent env=!dont-vary
</IfModule>
</IfModule>
Если вас интересует, почему эти файлы выглядят именно так - читайте документацию к модулям апача.
На другой ОС (у меня Ubuntu 9.04) эти файлы, наверняка, должны будут находиться в других местах. У себя я их разместил так, потому что эти конфиги автоматически подключаются вместе с подключением соответствующих модов апача (при использовании команды a2enmod).
По идее теперь сайт должен получше откликаться и, думаю, все-таки теперь он выдержит хабраэффект. :)
PS. С точки зрения клиентской оптимизации - css-ки и js-ки лучше бы собрать в один файл, а кучу запросов к изображениям заменить спрайтами. Заниматься первым особо не вижу смысла, т.к. и стилей и скриптов на блоге немного, а изображениями заниматься лень, т.к. все равно собираюсь переезжать на “Twenty Ten“.
Есть такая фича в Rubygems, как установка гем-ов в директорию пользователя ~/.gem, если gem install запускается без sudo и, соответственно, у пользователя нет разрешения установить что-либо в дефолтную директорию.
В Rubygems до версии 1.3.6 это решалось просто - автоматически. Т.е. если вы вместо sudo gem install пишете gem install - вам выдается warning, но гем успешно устанавливается:
[fxposter@kitty ~]$ gem install haml
WARNING: Installing to ~/.gem since /usr/lib/ruby/gems/1.8 and
/usr/bin aren't both writable.
Successfully installed haml-3.0.13
1 gem installed
Installing ri documentation for haml-3.0.13...
Installing RDoc documentation for haml-3.0.13...
Вроде бы всё замечательно. Но с версии 1.3.6 это дефолтное поведение изменилось, и при обновлении с помощью gem update --system об этом честно пишется:
–user-install is no longer the default. If you really liked it, see Gem::ConfigFile to learn how to set it by default. (This change was made in 1.3.6)
Но кто реально читают все эти тексты? Да и не все обновляют Rubygems таким образом. В нашем случае произошла смена сервера. Причем на старом сервере стояла старая версия Ruby Enterprise Edition и дефолтные Rubygems 1.3.5. На новом же серваке наши бравые админы опять всё “покомпилили” и вместе с REE у нас там обновился и Rubygems - теперь там стояла версия 1.3.7. И… сначала отвалился деплой, т.к. по умолчанию rake gems:install у нас там не включен. Запустили rake gems:install - не работает. Запустили gem install - не работает. Всё просто замечательно.
Разбирались долго. Сначала думали на админов, которые в рельсах плохо шарят и думали, что они там “понастраивали”. Кстати, понастраивали они действительно знатно - скомпилированный REE почему-то стоит поверх стандартного Ruby 1.8.5 (у нас там CentOS 5, в ближайшем времени переезжаем на Amazon EC2 на Ubuntu). Но это неважно. После долгого разговора с админом и ковыряния серваков я чисто случайно обратил внимание на разные версии Rubygems. Заподозрил неладное. Обновил их на старом серваке на 1.3.7 и… получил сообщение, написанное выше. Отлично. Теперь осталось восстановить поведение старых Rubygems.
В итоге всё оказалось довольно просто. В файл ~/.gemrc нужно дописать следующую строку: "install: --user-install". После чего мой .gemrc стал выглядеть так:
[waysgo@web-waysgo ~]$ cat .gemrc
---
:benchmark: false
:update_sources: true
:sources:
- http://rubygems.org/
- http://gems.github.com/
:bulk_threshold: 1000
:verbose: true
:backtrace: false
install: --user-install
Ну что ж, 5 лет учебы в университете закончились. Сегодня, а точнее уже вчера, прошла последняя защита - защита магистерских работ. Прошла она для всей нашей группы, в принципе, успешно - все защитились на пятерки, что не может не радовать. Я же получил свои скромные 98 баллов из 100 и остался этим вполне доволен.
До получения диплома осталось всего ничего - одни формальности - заполнить обходной лист, принести справку с места работы, может еще что-нибудь, ну и получить диплом, естественно. Но самое главное, что мне наконец-то не нужно думать о том, что я должен что-то кому-то делать и сдавать. Теперь можно наконец-то уйти с головой в работу (или в отдых :)) и заняться своими проектами!
Осталось только пожелать всем моим одногрупникам и одногрупницам, ну и себе заодно, удачи в будущих начинаниях. :)
На работе столкнулись с “особенностью” работы jQuery.live(), на которую хотелось бы обратить внимание, потому как, судя по всему, отнюдь не все о ней знают (и в результате чего пишут неработающий код).
Итак, простой пример - навешивание двух событий на один и тот же элемент:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-type" content="text/html; charset=UTF-8" />
<title>jQuery.live() test page</title>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript">
jQuery(function() {
$("a").bind("click", function(e) {
alert("a");
return false;
});
$("a").bind("click", function(e) {
alert("b");
return false;
});
});
</script>
</head>
<body>
<a href="http://blog.fxposter.org/">Блог FX-а</a>
</body>
</html>
Пример можно посмотреть здесь. В результате клика на ссылку - получаем 2 alert-а, всё хорошо, ожидаемо и предсказуемо.
Переписываем код для работы с jQuery.live(). Для тех, кто в танке - live() вешает событие не на сам элемент, а на document. В результате bubbling-а событие, которое произошло над каким-либо элементом поднимается вверх по DOM-дереву и соответственно вызывает обработчики всех элементов, которые оно встретит. Если вы и этого не знали - то вам не нужно читать мой блог, а пора идти и покупать книгу по JavaScript-у (мне, кстати, тоже давно пора, но всё никак не соберусь). Итак, в конце концов событие доходит до document-а и обработчики вызываются у него. Обработчик, который устанавливает jQuery.live() проверяет - соответствует ли event.target (а именно здесь хранится обьект DOM-дерева, с которым произошло событие) соответствующему селектору (в данном случае - это селектор “a”) и если соответствует - то выполняет обработчик.
Преимущества и недостатки - это тема отдельной статьи. Если не уклонятся в сторону оптимизации, то основным преимуществом, на мой взгляд, является тот факт, что обработчики, навешенные live()-ом будут запускаться даже для элементов, которые были динамически добавлены на страницу, в отличии от bind()-обработчиков, которые на эти элементы нужно будет навешивать вручную (если непонятно, почему это работает именно так - читаем предыдущий абзац, если все равно непонятно - идем покупать всё ту же книгу).
Далее - зачем нужен “return false” в конце обработчика? Он предотвращает от того, чтобы вызывалось действие по умолчанию (в данном случае - переход по ссылке и событие не поднималось выше). Чаще всего JS-разработчики вообще не думают о bubbling-е и под “return false” понимают только “отмену действия по умолчанию”, ну или они вообще не знают, что именно происходит и пишут “return false”, потому что так работает.
Такое отношение jQuery частенько прощает. Но не в случае с live()-методом. Попробуем запустить следующий пример:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-type" content="text/html; charset=UTF-8" />
<title>jQuery.live() test page</title>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript">
jQuery(function() {
$("a").live("click", function(e) {
alert("a");
return false;
});
$("a").live("click", function(e) {
alert("b");
return false;
});
});
</script>
</head>
<body>
<a href="http://blog.fxposter.org/">Блог FX-а</a>
</body>
</html>
В результате клика теперь выскакивает только один alert. Пора обратится к документации:
# To stop further handlers from executing after one bound using .live(), the handler must return false. Calling .stopPropagation() will not accomplish this.
Хаха. В данном случае jQuery интерпретирует false “несколько иначе”. :)
Для того, чтобы “пофиксить” подобный баг нужно обратится все к тому же bubbling-у и обработке событий и сделать именно то, что предполагается разработчиком - “отменить действие по умолчанию”. Это делается с помощью метода event.preventDefault() (пример):
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-type" content="text/html; charset=UTF-8" />
<title>jQuery.live() test page</title>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript">
jQuery(function() {
$("a").live("click", function(e) {
alert("a");
e.preventDefault();
});
$("a").live("click", function(e) {
alert("b");
e.preventDefault();
});
});
</script>
</head>
<body>
<a href="http://blog.fxposter.org/">Блог FX-а</a>
</body>
</html>
И самое главное (барабанная дробь!) - при использовании bind() для навешивания обработчиков preventDefault() тоже можно использовать!
Наткнулись мы на эту “фичу”, когда у нас почему-то перестали вызываться некоторые обработчики
Напоследок, замечу еще одно - элемент, на который навешено хотя бы один обработчик события через bind() с “правильно работающим return false”, никогда не будет вызывать никакие live()-события. ;)
Так что будьте бдительны и не забывайте об особенностях обработки событий в JS. Удачи.
Недавно писал проект на Rails 2.3 + MongoDB + MongoMapper. Не могу сказать, что все было хорошо - для того, чтобы существующие плагины для рельсов заработали с MongoMapper-ом пришлось немного повозиться, но в итоге все закончилось хорошо. :)
А мой сегодняшний рассказ пойдет о некоторых особенностях MongoDB и MongoMapper-а, с которыми вы, скорее всего столкнетесь, если будете использовать эти библиотеки.
Для начала поговорим немного о MongoDB. Что это такое?
MongoDB — документо-ориентированная система управления базами данных (СУБД) с открытым исходным кодом, не требующая описания схемы таблиц. Написана на языке C++. СУБД управляет наборами JSON-подобных документов, хранимых в двоичном виде в формате BSON.
Если попробовать вкратце охарактеризовать эту базу данных, то получится что-то вроде этого: аналог реляционной СУБД без join-ов и транзакций, зато с поддержкой структур данных (массивов, хешей).
MongoMapper - это “ORM” для MongoDB, написанный на руби. “ORM” в кавычках потому что сложно себе представить ORM для нереляционной базы данных. Я бы скорее назвал это высокоуровневой оберткой над API, которое предоставляет MongoDB, с поддержкой ассоциаций между записями и много чем еще.
Теперь, собственно, о “особенностях” MongoDB и MongoMapper-а.
Вложенные документы
Вложенные документы в понятии MongoMapper-а - это когда одни обьекты хранят внутри себя другие. Для примера, возьмем следующую модель:
class User
include MongoMapper::Document
key :login
key :password
key :salt
many :posts
many :addresses
end
class Post
include MongoMapper::Document
key :title, String
key :body, String
timestamps!
end
class Address
include MongoMapper::EmbeddedDocument
key :type, String
key :country_id, ObjectId
key :city_id, ObjectId
key :city_address, String
end
Чтобы понимать, что тут происходит нужно, во-первых знать Ruby :) и во вторых - прочесть пост John Nunemaker (даже не знаю, как правильно перевести :)) о MongoMapper-е. Вот еще один, кстати, тоже интересный.
Таким образом имеем модель пользователя, которая хранить набор постов, написанных этим пользователем и набор адресов пользователя (домашний, рабочий, etc).
Особенности работы со встроенными документами легче показать на примере:
user = User.first
user.posts
Последняя комманда вернет “scope”/”relation”, а не просто массив элементов (люди, знакомые с named_scope в ActiveRecord версии < 3 поймут).
Соответственно, можно дродолжить эту комманду, например, так:
user.posts.all(:conditions => { :created_at => { "$gt" => (Date.today - 10.days) } })
В то время как
user.addresses
вернет массив и всю дополнительную фильтрацию прийдется производить с помощью Ruby.
Манипуляции со вложенными документами
У EmbeddedDocument нет понятия “id”, т.е. отличать обьекты Address один от другого можно только по их индексу в массиве user.addresses либо вводя “свои” идентификаторы. Но даже в этом случае выбирать соответствующий обьект прийдется “вручную”:
user.addresses.detect { |address| address.identifier == params[:address_identifier] }
PS. Для тех, кто в танке: detect - это аналог select { |address| … }.first.
Соответственно - вложенные обьекты легко добавлять, выбирать всю связь полностью, но с ними становится “сложно” работать, как с отдельными обьектами. В частности - неудобно обновлять из-за проблем с выборкой отдельного элемента и неудобно удалять.
С удалением вообще интересная история. Удалить обьект имея только ссылку на него - нельзя, нужна еще и ссылка на массив, в котором он хранится:
user.addresses.first.delete/destroy
не работает, т.к. таких методов у вложенных обьектов нет. Нужно делать так:
user.addresses.delete_at(address_index)
если вы знаете индекс в массиве адресов, либо
user.addresses.delete_if { |address| address.identifier == address_identifier }
если знаете идентификатор или удаляете по какому-либо другому полю.
Несложно, но проблемы на первых порах с этим бывают.
“идентификаторы” обьектов MongoDB
Каждому обьекту в бд MongoDB присваивает свой идентификатор. Как он по умолчанию строится можно посмотреть здесь. Обычно эти идентификаторы совсем не мешают, но иногда хочется просто засунуть вместо них какие-нибудь данные.
Например, вместо такой модели:
class Tag
include MongoMapper::Document
key :name, String
end
иметь такую
class Tag
include MongoMapper::Document
# id-шник явно никогда не прописывается
end
И создавать обьект так:
Tag.create(:_id => "ruby on rails")
MongoDB это позволяет делать. Более того - драйвер Ruby для MongoDB это тоже может делать. Проблема в том, что это не умеет делать MongoMapper. Совершенно. Как обойти эту проблему я на данный момент не знаю (я в итоге сделал модель первого типа и забил), есди кто знает решение - напишите, будет интересно.
И напоследок поговорим о
Has And Belongs To Many в MongoDB
… или habtm, знакомый многим по ActiveRecord.
Приведу пример из моего проекта: есть домены, есть их модераторы, связть many-to-many. Для каждого домена хочется видеть список модераторов и для каждого модератора хочется видеть список доменов, которые он модерирует.
Честно говоря, задача поставила меня в тупик. Т.е. как все мы решаем эти задачи с помощью RDBMS? Правильно - связующей таблицей. Так же можно было поступить и здесь, но… Как-то это было некрасиво, на мой взгляд, использовать этот подход вв документоориентированной базе данных.
Первое решение, которое мне пришло в голову - хранить массив id-шников доменов для каждого пользователя и хранить массив id-шников пользователей для каждого домена и синхронизировать их.
Последние два слова очень смущали - синхронизация вносила излишнюю сложность. Т.е. реализовать-то её можно, но очень не хотелось.
Собственно, обратился за помощью к знакомым и вышел на одного человека (ник - Necromant, сайта его я не знаю :) ), который помог решить мне эту проблему.
Итак, решение - хранить только один список id-шников (или в юзерах или в доменах) и сделать по нему индекс, соответственно, вывод как доменов по юзеру так и юзеров по домену будет достаточно быстрый.
Я хранил массив юзеров для домена, т.к. если бы я сделал иначе, то при удалении домена пришлось бы пройтись по всем юзерам и удалить все связи на домен, а в выбранном варианте этого делать не нужно (а удаление юзеров - это пока что вообще не существующее событие для моего проекта):
class Domain
include MongoMapper::Document
# ...
key :moderator_ids, Array, :index => true
def moderators
User.find moderator_ids
end
end
class User
include MongoMapper::Document
# ...
def moderator_of?(domain)
domain.moderator_ids.include? id.to_s
end
def moderated_domains
Domain.all :moderator_ids => id.to_s
end
end
Еще хочу рассказать о том, как подружить MongoMapper и Clearance, но пост вроде и так не маленький получился, так что ждите еще один пост о MongoMapper-е в ближайшие дни.
PS. Совсем писать разучился… :(
PPS. Если вам понравился этот пост, проголосуйте за него на Habrahabr-е. :)
Настало время возродить этот блог. За более чем полугодие на нем не появилось ни одной статьи и я наконец-то намерен прекратить этот “застой”. Да, по сравнению с временами, которые были раньше у меня стало существенно меньше времени, чтобы писать что-то большое и серьезное - работа, университет (если быть точнее - то написание диплома) и еще один “секретный сайд проект” о котором, возможно, я когда-нибудь всем расскажу.
Начну с глобальных изменений, которые произошли за последнее время в моей жизни и которые однозначно коснутся этого блога.
Ну, во-первых, я теперь не пишу на PHP. Вообще не пишу. И не очень сильно слежу за последними веяниями моды, связанными с этим языком. Да, я все равно иногда появляюсь в группе симфонистов (кому нужен доступ туда - пишите, там новичков любят и на многие нубские вопросы отвечают :) ). Да, я иногда посматриваю на фреймворки, которые сейчас являются популярными в сфере PHP - всякие Yii, ZF, etc. Посматриваю исключительно для того, чтобы быть в курсе происходящего. Учить их и писать на них в общем-то не очень хочется.
На что я променял PHP? Естественно на Ruby. Я не восхищаюсь этим языком, не превозношу его над остальными, просто на работе я пишу на нем (кстати, я работаю над стартапом для бизнесов - WaysGo, запуск будет, я надеюсь, очень скоро) и ни на чем другом желания писать особо не возникает - с точки зрения веб-разработки Ruby меня полностью устраивает. Этот язык удобен и достаточно экспрессивен. А Ruby On Rails - отличное средство для тех, кто хочет писать веб-приложения. Ну, в общем, вы меня поняли. ;)
Кстати говоря, я немного соврал насчет того, что я пишу на Ruby на работе. С начала этой недели я пишу iPhone-приложение на Objective-C и, в связи с этим, шустренько осваиваю Mac, Xcode, сам Objective-C и всякую прочую фигню, которая нужна для разработки под iPhone. Определенного мнения по поводу мака я пока сказать не могу, а вот Objective-C с первого подхода отличается похожестью на руби с точки зрения вызовов методов у объектов и открытых классов, но в то же время отличается несколько некрасивым синтаксисом (квадратные скобки, рррррр). С другой стороны, к синтаксису Ruby у меня тоже изначально было плохое отношение. Это отношение, кстати, не изменилось - я по прежнему считаю, что Ruby - непонятный язык для новичка, и если код на, например, Python можно просто читать, то для того, чтобы почитать код на Ruby придется сначала посмотреть туториалы, или что-то в этом роде, потому что синтаксис кроме выразительности отличается еще и неочевидностью. После пары дней работы с Ruby проблемы отпадают сами собой, так что этот аспект меня уже не очень беспокоит. Собственно, я надеюсь, что рано или поздно я перестану замечать синтаксис Objective-C и буду просто писать код, который делает что мне нужно.
Вот, собственно, и все на сегодня. Вроде ничего не забыл. До скорого! :)







Последние комментарии