Первый раз первый класс!

11.11.2010

Ну наконец-то. Начал писать ещё 11 октября и вот целый месяц прошёл! Я в шоке, снова обожгло осознанием своей ужасно тормознутости. МЕСЯЦ! Это пиздец, извините.

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

Класс для работы с API AvisoSMS

Я ещё и git и github начал осваивать, в следующей заметке напишу вводную. В общем, код класса здесь — github.com/BrokenBrake/classAvisoPrefixes/. Можете просто скачать, можете отпочковывать свои ветки, слать какие-нибудь баг-репорты и всё такое. Open Source.

Скорей всего это не последняя версия, потому что я очень жду и надеюсь на критику программистов. Заметили какие-то косяки? Не молчите, пожалуйста. Это мой первый блин в ООП. Я вот уже вижу одну несуразицу: глупо выдавать ошибку при повреждённом файле кэша, его нужно просто тихонько переписать. Я так думаю.

Методы класса

Особенность реализации класса: все открытые методы выдают значение (число != 0, массив данных, строка) или информационное сообщение при нормальных условиях. Если хоть какая-то ошибка, метод возвращает False,
а значение ошибки получаем через метод errorMessage(). Кроме того есть метод errorID, возвращающий числовой код ошибки (удобно использовать для условий).

  1. useCache()
    Использовать кэш. Можно передать массив аргументов, в котором будет желаемый относительный путь к файлу кэша (file), и/или время его жизни в секундах (live). По умолчанию значение file = var/classAvisoPrefixes.cache, а live = 86400 (кэш будет обновляться раз в сутки). Если вы не используете проверку цены SMS на номер, смысла в кэше нет. Естественно, файл кэша должен быть доступен для записи.
  2. errorMessage()
    Получить значение ошибки (False, если ошибок не было).
  3. errorID()
    Получить цифровой код ошибки (аналогично errorMessage).
  4. allNumbers([$coverage])
    Получить массив всех возможных номеров. О ключах и значениях не буду здесь, можете прочитать в комментариях класса, прямо в начале исходников. В метод можно передать необязательный параметр-фильтр $coverage, со значениями: ru, ua, ru-ua (подробности там же).
  5. numbersAroundCost($sum [, $coverage])
    Получить короткие номера телефонов стоимостью для абонента в районе $sum рублей (с НДС). Выдаёт массив из двух ближайших номеров, формат массива аналогичен allNumbers + добавляется параметр diff – отличие цены от заданной суммы. Вторым параметром также может передаваться охват (ru, ua, ru-ua).
  6. profitFromNumber($number)
    Средняя сумма выплаты вебмастеру (в рублях).
  7. costForNumber($number)
    Формат аналогично profitFromNumber, но выдаёт максимальную для абонента цену.
  8. costFromTo($number)
    Получить строку вида «от N руб. Z коп. до X руб. Y коп.» для номера. Естественно, это значения конечных цен для абонента (с НДС). Удобно использовать в подсказках абоненту, не обрабатывая результат
    выдачи costForNumber().
  9. checkSignal($signal, $check [, $reply])
    Проверить сигнал от AvisoSMS. Первым аргументом удобно делать массив $_REQUEST, во втором массиве вы можете передать любые параметры, которые хотите сравнить. Для справки читайте описание API. Метод возвращает $reply (OK по умолчанию) или False.

Что дальше?

Ругайте, пожалуйста! Хорошенько. Очень нуждаюсь в толковой критике, повторяю — это мой первый блин, поэтому наверняка я налепил много новичковых глупостей. Тем не менее, класс полностью работоспособен, я протестировал его. Класс выполняет свою работу.

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

Комментарии

  1. # samlowry

    Мельком просмотрел класс. В именах методов, насколько я знаю, принято указывать явнее действие. То бишь глагол + существительное. Типичные действия — get, put.

    У тебя пачка методов зовётся просто существительными: allNumbers, errorID. При этом другие методы зовутся checkSignal, readCache и getPipe. А если надо будет setAllNumbers — то как потом разбираться, что получить — это allNumbers а не getAllNumbers?

    ЗЫ: как вообще с гитом работать было?

  2. # Тормоз

    У меня английский очень хромает. С именами, действительно, не всё гладко, согласен.

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

  3. # aleksei: 

    Если уж совсем по уму делать, надо в случае ошибки генерить эксепшен, а не возвращать фолс. А то как булевское значение вернуть? :)

  4. # Тормоз

    False в случае ошибки.

  5. # marazmiki: 

    0. Самое, на мой взгляд, ужасное – нет тестов. Это ведь не только самоконтроль и гарантия того, что код делает всё положенное, но и неплохая документация по API с use-кейсами.

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

    2. Методы класса длинноваты. Чисто субъективно: если метод занимает больше 10-20 строк, его надо дробить на более мелкие. Рекомендуется методы делать атомарными, то есть один метод – одна маленькая неделимая функциональность. Эти лямбды, которые нифига не лямбды, тоже можно было бы вынести, пожалуй. Кстати, в эру php5.3 можно было бы не ущербный create_function использовать, а нормальные анонимные функции :)

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

    4. Не стоит хардкодить строки и числа в коде. Это касается и мифической пятёрки на 481-й строке, и к урлу пайпа. Ну а про коды ошибок уже в коде отмечено.

    Про стандарты оформления и именования ничего говорить не буду, это всего лишь рекомендации. Главное – придерживаться одного стиля.

    Сходу больше ничего не придумывается :)

  6. # Jeck

    Блокировки ошибок с помошью @ – зло, где можно лучше дополнительную проверку сделать.

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

    По поводу констант ошибок – сделай константами класса в чем проблема?
    const FETCH_DATA_ERROR = 1;
    и потом везде AvisoPrefixes::FETCH_DATA_ERROR

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

  7. # aktuba

    /*********************

    • Получить номер(a) для цены
    */
    private $limit = 2; // сколько номеров в выдаче?
    public function numbersAroundCost($sum, $coverage = ‘’)

    жесть… если уж объявляешь так жестко – вынеси в начало класса.

    >Блокировки ошибок с помошью @ – зло, где можно лучше дополнительную проверку сделать.

    Полностью согласен. try..catch в помощь, если не уверен.

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

  8. # polonskiy: 

    Зачем юзаешь трубы? Без них никак?

  9. # AntonYu: 

    Хорошо бы еще все в phpDoc завернуть. Ну и юнит-тесты да, тоже неплохо бы.

  10. # Evgeny Sergeev

    aktuba, так тут явно несколько сущностей! И классов должно быть несколько.

  11. # Young: 

    Как уже говорили выше – есть смешивние ОПП подхода и процедурного.

    Получение ошибки с помощью вызова дополнительной функции собственно как раз процедурный подход.Если уж использовать ООП – то либо эксепшины (они есть в php?), либо возвращать объект нужного типа.

    Т.е. если фукция возвращает эземпляр класса ЧтоТоТамОшибка – то это ошибка. И сама ошибка храниться в нем.

    Это позволит легко и просто передавать ошибки на уровень выше или вообще в другой модуль.

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

  12. # aktuba

    Evgeny Sergeev, в данном случае – да. Как минимум – кеш необходимо вынести. А вообще, правильнее сделать абстрактный класс (интерфейс), который выполняет общую работу (инициализация, подключение кеша и т.д.) и наследоваться от него. Но это уже выходит за рамки данного поста – тут Тормоз опубликовал первый свой класс, а не либу =).

  13. # Тормоз

    Всем спасибо! Сейчас внимательно прочту и отвечу. У меня отключали электричество (как в деревне, блин, два раза!), поэтому я решил поспать :)

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

    Джек, я сейчас уже не помню что именно не получилось, но почему-то с константами класса не вышло. В следующий раз попробую. А ещё про собачку — ну зачем городить проверку и вывод ошибок для простейшего счётчика ++$ID? Это же совсем элементарная штука. И вторая собачка в file_put_contents тоже оправдана, потому что ошибка с доступом выявляется до вызова этой функции. Я так думаю.

    Aktuba, как раз всё помещать в начало — дурной тон (хотя и такое у меня есть). Я согласен с тезисом о том, что переменная должна объявляться максимально близко к месту использования.

    try..catch в помощь, если не уверен

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

    Зачем юзаешь трубы? Без них никак?

    Какой-то странный вопрос. Без них КАК, но с ними элементарно удобнее. Назови хоть одну причину, по которой мне не стоит пользоваться любимыми Yahoo Pipes.

    Что в целом? Как для первого блина, совсем всё плохо или бывает хуже? :)

  14. # Миша: 

    >> Если хоть какая-то ошибка, метод возвращает False,
    а значение ошибки получаем через метод errorMessage().

    Дальше не читал

  15. # Evgeny Sergeev

    >Что в целом? Как для первого блина, совсем всё плохо или бывает хуже? :)

    Тормоз, то что бывает хуже – факт.

    У меня предложение – давай вместе отрефакторим этот класс?

  16. # Тормоз

    Эх, Миша, Миша. Вроде адекватный был такой, а ведёшь себя как тролль :) Я же просил покритиковать, а не просто кидать помидорами. Чего тебе не понравилось?

    Евгений, давай! А как вместе? Я не очень представляю себе синхронизацию этого процесса. Я планировал начать писать класс-кэшер, а потом уже переделать этот. В письме написал.

  17. # aktuba

    ЮAktuba, как раз всё помещать в начало — дурной тон (хотя и такое у меня есть). Я согласен с тезисом о том, что переменная должна объявляться максимально близко к месту использования.

    Ну и зря… У меня привычка еще с Delphi осталась и очень помогает:

    1. сначала описываем константы
    2. описываем переменные (public, protected, private – именно в таком порядке)
    3. описываем методы (private, protected, public).

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

  18. # marazmiki: 

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

    Собачки в коде там, где без них можно обойтись – действительно зло. В том числе по соображениям ресурсоёмкости. Фактически одна собачка суть локальное переключение error_reporting‘а на E_NONE и обратно на предыдущее значение. Это типа затратно.

  19. # Тормоз

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

    Про красоту кода мне нравится статья от Алика Кирилловича. Кстати, ты там вроде был в комментах, не помню, давно читал.

    Marazmiki, обязательно приду к тестам и обещаю отказаться от собачек :)

  20. # Young: 

    Тормоз, если у тебя переменная используется в одном методе – то это нормально ее объявить рядом с этим методом. Но все же, если она используется в нескольких методах ( иначе зачем ее объявлять вне метода???) – то понятие близко к месту использовать становиться не очевидным.

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

    А Мишу прости, ибо получении ошибки через errorMessage (getErrorMessage тогда уж) и ООП – две сущности которые в одном предложении выглядят дико. И навивают мысли что ООП для котого это просто использование классов, при старом процедурном подходе :)

  21. # polonskiy: 

    Я не знаю зачем нужны трубы. Мне лениво там регистрироваться. У меня сложилось впечатление, что они нужны тем, кто не умеет программиировать. Какую функцию они выполняют у тебя?

  22. # ASH

    о, прикольно :) Правда APIv2 выйдет скоро, но для разминки неплохо)

  23. # samlowry

    А между тем, @ на некоторые файловые операции есть прямо в примерах в официальном мануале (и я вроде даже её видал в бекап-модуле Zend Framework).

    Тормоз, да, поведай нам — что там в пайпах вообще происходит? А то проект опенсорсный, а часть функционала спрятана.

  24. # Тормоз

    Young, блин! Ну так ты скажи как надо делать в этом случае? :) Какое твоё мнение по поводу вывода ошибок в ООП? Try/catch?

    Polonskiy, у тебя сложилось неверное впечатление. Но в данном случае, в принципе, можно было бы обойтись и без Pipes, однако мне так просто удобней. Трубка конвертирует json в сериализованные данные PHP и отфильтровывает российские и украинские номера (Беларусь остаётся за кадром).

    ASH, так, а тут подробнее, пожалуйста! Что за APIv2? Надеюсь, старое API при этом не накроется?

    Samlowry, да ничего не спрятано — смотрите. URL же есть в исходниках.

  25. # Young: 

    Дык, я же вроде сказал. Либо библиотеку делать с использование экцепшинов. Т.е. при ошибках кидать нужные экцепшины (т.е. по сути объектны некоторые).

    Либо возвращать некоторые объекты руками – т.е. по сути более простой аналогичный механизм.

    В яве это бы выглядело бы так.

    1. В варианте с exception – бы бы сделал несколько класссов наследников (CacheInitExeption и т.п.) от стандартного класса exception и бросла бы их (throw) в случае ошибок, а там бы пользователь библиотеки смотрел бы ловить их или нет и на каком уровне.

    2. Либо, что возможно к php ближе, хотя мне не нравиться – возвращал бы объекты бы определенных классов.

    Т.е. опять же бы в яве было бы так

    Object response = numbersAroundCost

    if (response instanceof AvisoErrorRespone) sendErrorToVisualizator(response)
    else
    { AvisoNumbersCost numbersCost = (AvisoNumbersCost) response;

    numbersCost->getNumberCostByShortName(«premium»); }

    P.S. Кстати по поводу труб и программирования. Я лично считаю что это нормально в первой версии использовать различные вспомогательные вещи с которыми знаком, даже если возможно они работают не самым оптимальным способом. Степ бай степ – это самое главное.

  26. # Тормоз

    ОК, буду изучать это всё дело. Спасибо. А чем именно такой подход лучше, чем мой errorID()?

  27. # Young: 

    Тем что у тебя сообщение о ошибке отчуждаемый элемент и его можно дальше передать по цепочке.

    Смотри сейчас мы создали экземпляр твоего класса, сделали запрос – получили ошибку. Далее теперь нам нужно это ошибку где-то залогировать или где-то отобразить. Нам нужно передать экземпляр твоего класса в логер, и там его спросят об ошибке и выведут ее. А если у нас асинхронное использование? И во время между вызовом одиного метода и визуализацией его ошибки вызовут другой метод – все информация о ошибке либо потерянна, либо неверна что еще хуже.

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

    В конкретном случае и класс не большой, и ошибка в виде константы (а может быть более сложной – содержать в себе например полностью дамп состояния объекта) – можно и класс передать и вообще константу скопировать. Но у нас же есть промежуточная задача в ООП потренироваться – то почему бы правильно не сделать.

    Т.е. в данном конкретном случае (особенно если не думать о расширении) подход не лучше, а просто более правильный.

  28. # Young: 

    Далее, смотри. Допустим – у нас нужно обрабатывать различные ошибки на различном уровне.

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

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

    С экцепшинами это все достататочно элегантно делается.

    Т.е. ты часть экцепшинов перехвытываешь, часть пропускаешь на более высокий уровнь и так далее.

    Ручками это сделать – не так просто, везде городить после вызова проверку if (getError())
    {
    } else
    {
    }

    Тяжко это.

    Ну и еще можно много чего вспомнить…но 2 часа ночи тут у меня. Это все конечно может где-то оверхед для конретно это задачи – но помоему вполне ложиться сюда.

  29. # Тормоз

    Проясняется, теперь почти всё понял. Спасибо! Стану умнее, следующая версия будет лучше :)

  30. # ASH

    Тормоз, нет, не накроется, будет работать параллельно, но во второй больше вкусностей. Как будет разрешено показывать – пришлю линк.

Комментирование этой статьи закрыто.

Интересное Покупки ТехникаРазное Отдых Статьи Строительство Услуги Общество Хобби Культура Советы Уют