В выходные я начал работать над клиентской библиотекой Node.js для нового API Тошла . Старый побочный проект недавно перестал работать, и мне нужно это исправить, потому что жизнь без частых писем о моих деньгах дезориентирует меня до чертиков.
Бета API Toshl открыт в июле хорошо продумана, имеет хорошую документацию и предоставляет все, возможно , хотят. Я люблю хорошо раздробленную систему разрешений.
Но для пользователей моей библиотеки Node.js я хотел сделать жизнь еще проще. Давайте посмотрим на получение расходов.
После аутентификации — тестирование клиентов OAuth — отстой, и Toshl очень часто делает ваш токен недействительным . Думаю, мне пришлось обновить его четыре раза за пять часов веселья. — вы можете поговорить, чтобы https://api.toshl.com/expenses
получить список расходов для текущего пользователя.
Это вернет последние 30 записей.
Но есть куча вариантов. У вас есть нумерация страниц, вы можете установить конкретные to
и from
даты, вы можете фильтровать вещи по тегам и не теги.
Как библиотека может сделать это простым в использовании?
Один из подходов состоит в том, чтобы предложить способ указания хэша опций при вызове функции, но можем ли мы сделать его еще проще?
Ответ заключается в полиморфизме.
Допустим, у вас есть функция с именем toshl.expenses
, в Haskell вы можете сделать что-то вроде этого:
expenses::Result expenses = general_expenses "" expenses::Number -> Result expenses N = general_expenses "?per_page="+str(N) expenses::[String] -> Result expenses tags = general_expenses "?tags="+(tags.join "&") expenses::Date -> Date -> Result expenses from to = general_expenses "?from="+str(from)+"&"+str(to) expenses::Json -> Result expenses params = general_expenses "?"+to_query(params) general_expenses::String -> Result general_expenses query = make_request "/expenses" query make_request::String -> String -> Result make_request endpoint query = ;; do stuff to read from full URL
Синтаксис, скорее всего, неправильный, но вы понимаете, на что я указываю. Вы всегда можете вызвать expenses
аргумент, который вас интересует, и он волшебным образом создаст вызов более обобщенной версии функции.
Вы можете сразу определить, что expenses
будет делать вызов в каждом случае, и проверить, какой тип аргументов принимает функция, тривиально. Даже тот, кто не знаком с Haskell, может понять этот код.
Вот тот самый полиморфный код в Javascript . На этот раз это проверено, рабочий код.
exports.Toshl.prototype.expenses = function (params, to, callback) { var options = {}, query = ''; callback = arguments[arguments.length-1]; if (params) { if (typeof params == 'number') { options['per_page'] = params; }else if (arguments.length == 3) { options['from'] = util.iso_date(params); options['to'] = util.iso_date(to); }else if (params instanceof Array) { var tags = util.transform_tags(params); options[tags.type] = tags.tags; }else if (params instanceof Object) { options = params; ['from', 'to'].forEach(function (key) { if (options[key]) { options[key] = util.iso_date(options[key]); } }); } query = "?"+querystring.stringify(options); } this._request('/expenses'+query, callback); };
Ух ты, что?
Даже если вам очень удобно работать с Javascript, вам будет сложно понять, что происходит. Кажется, что большая часть функции имеет дело с переводом arguments
в a query
, а затем она предназначена this._request
для тяжелой работы.
Это самая чистая реализация, о которой я только мог подумать. Давайте исследуем.
Сначала мы убедились, что callback
это всегда последний предоставленный аргумент, который имеет смысл, когда вы ожидаете переменного количества аргументов. Все с одного до трех все в порядке.
Затем, если params
это число, мы используем его для построения ?per_page=N
запроса. Если есть три аргумента, мы используем первые два для построения ?from=Date&to=Date
запроса. Если первый аргумент является массивом, мы используем его для получения тегов, а если это объект, мы предполагаем, что он представляет хэш параметров для API.
В результате мы можем сделать это:
var toshl = new Toshl(); toshl.expenses(console.log); // prints last 30 expenses toshl.expenses(5, console.log); // prints last 5 expenses toshl.expenses("2013-10-01", new Date(), console.log); // prints all expenses between October 1st and now toshl.expenses(["coffee", "food"], console.log); // prints last 30 expenses tagged with coffee or food toshl.expenses({per_page: 10, tags: ["coffee", "food"]}, console.log) // prints last 10 expenses tagged coffee or food
Волшебство даты выполняется с помощью замечательной библиотеки moment.js — вы можете предоставить объект Date, строку даты или объект момента, но мы можем явно улучшить это, предполагая, что to
дата «сейчас», если ничего не указано.
Но код становится все сложнее.
Кто-нибудь знает лучший способ достижения полиморфизма в Javascript? Мне действительно нравится, когда я использую библиотеки, но я ненавижу реализовывать это таким образом …
Вы должны следовать за мной в твиттере, здесь .