Июн 23

Большой проект. Советы

Категория: Программирование

В моем понимании большой проект - это проект, который длится более одного года при участии как минимум трех программистов. Именно в таком проекте я работаю уже в течении ~ полутора лет. За этот срок я неоднократно сталкивался с разного рода “граблями”.

В двух словах о системе.

Система состоит из вебной и десктопной частей, обмен данными между которыми происходит посредством XML_RPC. Система требует наличия уникальных идентификаторов(GUID) для некоторых сущностей. Объем данных передаваемых с клиентской части на серверную достаточно велик, варьируется от кол-ва экземпляров сущностей созданных на клиенткой стороне. База данных содержит более сорока таблиц, в третьей части которых кол-во записей может превышать сто тысяч. База данных - PostgreSQL. Мультиязычность.

Далее список фишек, с которыми были проблемы или неясности.

1) GUID

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

  1. function getGUID(){
  2. $str = '';
  3. for ($i=0; $i <100; $i++){
  4. $rand = rand(1,255);
  5. $str .= $rand;
  6. }
  7. return md5($str);
  8.  
  9. }

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

2. UTF-8

Так как система нуждается в синхронизации со сторонними программами и должна реализовывать многоязычность, было решено использовать универсальную кодировку UTF-8. В связи с этим появились несколько проблем. Так как UTF-8 универсальная кодировка значит она должна позволять хранить любые наборы символов на любом языке, она это позволяет, но размер каждого из символов может варьироваться от одного байта до четырех, а для “хитрых” языков, типа китайский и того больше. Грубо говоря символ “a” на английской раскладке занимает в базе 1 байт, а сивол “а” на русской раскладке занимает 4 байта. Отсюда делаем вывод, что поле varchar(32) не сможет поместить фразу “УТФ навсегда”, кол-во символов в которой всего 12. Следует обратить на это особое внимание. Также нужно не забыть указать кодировку передаваемых данных в XML_RPC. Так как мы использовали XML_RPC от PEAR, при чем старой версии, пришлось лезть в сам файлик Server.php и там хардкодить строку

  1. /**
  2. * The present response's encoding
  3. * @var string
  4. * @see XML_RPC_Message::getEncoding()
  5. */
  6. var $encoding = 'UTF-8';

так как на команду setEncoding(’UTF-8′) он реагировал не адекватно.

3. Безболезненная расширяемость базы

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

1) если продавец один, мы его не идентифицируем и считаем по умолчанию все транзакции с его участием (расширяемый с большими трудностями вариант);

2) мы делаем дефайн этого самого продавца и присваиваем ему ID например равный 1. И во всех табличках, где должен участвовать продавец, добавляем поле в которое и вписываем эту единичку(недоделанный простой вариант расширяемости);

3) делаем табличку продавцов, в которой есть 1 продавец и его ID используем в качестве ID в пункте номер 2 (правильный вариант, удоборасширяемый).

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

4. INT(ID) и индексы

Ни для кого не секрет, что каждая табличка в базе, должна содержать автоинкрементный ID типа INTEGER. Некоторые разработчики этим полем пренебрегают, а зря. Так же важным моментом являются индексы.

5. Свои имена

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

6. Рефакторинг

Зачастую в больших проектах наступают времена, когда максимально срочно, нужно реализовать ту или иную функциональность за короткий срок. Вы реализуете это, но не так качественно как хотелось бы, так как времени у вас нет. Но так же часто, после таких срочностей наступают моменты “затишья”, когда особо делать то и нечего. Такие моменты идеально подходят для рефакторинга существующего кода.  Это конечно-же в идеале. Когда таких моментов не появляется, можете смело рассказывать заказчику о “костылестроении” и предупреждать, что нормальная система может превратиться в один большой костыль и потом всё равно прийдется выделять время на рефакторинг либо на переписку кода.

7. Чистка кода

Один раз за определенный срок (возьмем 2 месяца) необходимо проводить чистку кода. Например ранее вы использовали ту или иную функцию, а сейчас уже не используете, но так как вы не уверены в том, что эта функция используется только в вашей части проекта, вы её просто комментируете и ставите дату, когда вы её закомментировали. Если претензий типа: “У меня всё отвалилось” не поступало, значит при очередном осмотре кода, можете смело грохать эту функцию за ненадобностью. Можно конечно удалить, положившись на Subversion, но я предпочитаю использовать комментарии.

8. Даты создания/редактирования, активность записи

С давних пор выработал привычку, вне зависимости от таблицы в базе и её наполнения, хранить дату создания и редактирования записи. А так же стабильно добавляю булевской поле “активности” записи. Логика первых двух ясна. А поле активности записи используется для псевдоудаления записи. То есть все записи, у которых поле активности 0 не участвуют в выборках.

9. SQL - роботяга

Логика PHP или других скриптовых языков, работающих с базой максимально должна быть переложена “на плечи” базы данных. Благо современные базы очень много позволяют взвалить на себя. Не стесняйтесь почитать руководства по функциям, вьюхам, триггерам и тому подобному добру.

10. Взаимозаменяемая разработка

Есть большая система. Её разрабатывает несколько человек. Каждый занимается своей частью и не вникает в часть других разработчиков. Когда одно из звеньев выпадает из разработки(болезнь, отпуск), а по его части необходима доработка функционала, уходит много времени, что бы понять что к чему и всё таки внести доработки. Что бы такого избежать, нужно один раз в период(устанавливается в зависимости от ситуации лидером разработчиков) менять разработчиков местами или устраивать краткий обзор по коду, что бы каждый из них имел хорошее представление о своей части и более менее нормальное о соседской. На такие рокировки уходит определенное время, на оплату которого заказчик может не согласиться, но если такое сделать удается, эту возможность упускать не стоит.

11. Бинарные данные

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

12. Кеширование

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

13. “Левые” библиотеки

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

Вот в принципе и все, вспомню ещё - добавлю. Жду комментариев и дополнений. Спасибо

7 комментариев на данный момент

  1. COTOHA Июнь 24th, 2008 15:53

    1. а почему не просто автоинкремент? постгресс это умеет уже давно. да и самим это сделать легко.

    9. недавно видел выступление чертей из liveinternet.ru (http://www.rit2008.ru/paper_view.html?id=1797), так они наоборот в целях повышения производительности вынуждены отказаться от join’ов и мержить массивы средствами ПХП, т.к. это быстрее получается, чем грузить SQL. у них там правда какие-то сумасшедшие кол-во запросов в секунду.

    11. не понял - почему не советуешь?

    в остальном отлично.

  2. vitamin Июнь 24th, 2008 16:24

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

    >>9
    Не могу не согласить с докладчиками, сам одно время даже отдавал предпочтение применению логики PHP для обработки нескольких выборок, но это скорее всего было связано с недостаточным кол-вом знаний по работе базы данных. В принципе нужно смотреть по ситуации, изначально я бы посоветовал делать джоины, ну а потом, если ситуация обяжет перенести часть логики на PHP. Разделить существующие джоины на несколько запросов и потом обработать их как по мне менее трудозатратно, нежели имея логику разворачивать джоины.

    >>11
    Писал этот пункт основываясь на своем опыте хранения бинариков в базе. Так как ворочать приходилось большими объемами записей, выборка десяти тысяч записей в каждой по одному бинарному полю размером от 10 до 40 Кб занимала в полтора раза больше времени и ресурсов нежели выборка того же кол-ва с полем-ссылкой на бинарик. Исключать это поле можно было, но перечисление 30-40 полей в селекте напрягало.

  3. COTOHA Июнь 24th, 2008 19:27

    по 11 - насколько я знаю нормальная база, а постгрес вроде такая BLOB не фетчит при select *, а фетчит уже при обращении к нему.

  4. vitamin Июнь 24th, 2008 19:35

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

  5. vitamin Июнь 24th, 2008 19:39

    Только что проверил в pgAdmin’е. Подгребает всё сразу, в том числе байт-ареи заполненные.
    // PostgreSQL 8.0.8

  6. Scorpion Июнь 25th, 2008 10:45

    >>Ни для кого не секрет, что каждая табличка в базе, должна содержать >>автоинкрементный ID типа INTEGER. Некоторые разработчики этим >>полем пренебрегают, а зря.
    Эт наверное про меня, но я по прежнему не согласен с этим.

  7. Scorpion Июнь 25th, 2008 11:32

    Подробнее о GUID-ах

    Изначально была выбрана кривая реализация GUID в АПС (varchar 32), это была одна из граблей как на стороне веб так и на стороне десктоп.

    По сути же своей GUID (UUID) является бинарным полем длинной в 16 байт (ну или для более простого понимания как 16 * 8 = int128), а пользователю представляется в виде форматированного инта в 16-ричной системе исчисления, собственно как и хэш MD5.

    Майкрософтский алгоритм генерации GUID генерирует уникальный глобальный идентификатор. Собсно читайте тут http://ru.wikipedia.org/wiki/GUID повторятся не буду. В PosgteSQL реализация несколько иная (http://www.postgresql.org/docs/current/static/datatype-uuid.html). Для поддержки uuid в постгресе необходимо пнуть админов, что бы они собрали постгрес с поддержкой библиотеки libuuid (http://linux.die.net/man/3/libuuid), а так же вам прочитать мануал по использованию функций libuuid в постгресе (http://www.postgresql.org/docs/8.3/static/uuid-ossp.html)

Оставить комментарий

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