среда, 5 ноября 2014 г.

Иконка скайпа в Ubuntu 14.04

В Ubuntu 14.04 c Unity устанавливаем скайп из deb-пакета. Запускаем, не появляется иконка в системном трее.

Проблема связана с тем, что в Ubuntu 14.04 вообще нет никакого системного трея, поэтому даже отлично работавшие ранее способы с изменением настроек через dconf-editor уже не помогают. Помогает установка следующего пакета.

sudo apt-get install sni-qt:i386

sni-qt - плагин Qt, который превращает все старые QSystemTrayIcon в новые модные StatusNotifierItems. Теоретически решает проблемы не только скайпа, но и всех приложений, работавших когда-то с системным треем.

вторник, 2 сентября 2014 г.

Первый Google Apps Script

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

Для написания своих скриптов к Google Apps существует специальный клон JS - Google Apps Script. Его использование не требует установки дополнительного ПО. Писать код, отлаживать и запускать можно прямо в браузере.

Для создания скрипта нужно открыть в браузере Google Disk, нажать кнопку "CREATE" и выбрать в выпадающем списке пункт "Script". Если такого пункта нет, то нужно подключить приложение Google Apps Script к диску. Делается это с помощью диалога управления приложениями, запускающегося из меню настроек диска. В процессе создания нового скрипта будет предложено выбрать один из готовых шаблонов для популярных приложений. Можно поэкспериментировать с шаблонами, а можно выбрать "Blank Project". Особого смысла в использовании шаблонов я не увидел.

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

Назовём проект "autounzip". Так же назовём и функцию, которая будет содержать весь код, реализующий задуманное.

function autounzip() {
  
  // Проверяем все папки 'tmp' на гугул-диске
  var folders = DriveApp.getFoldersByName('tmp');
  while (folders.hasNext()) {
    var folder = folders.next();

    // Ищем все сохранённые zip-архивы
    var contents = folder.searchFiles('mimeType="application/zip"');
    while (contents.hasNext()) {
      var fileContents = contents.next();
      var unzipblobs = Utilities.unzip(fileContents.getBlob());
      for (var i = 0; i < unzipblobs.length; i++) {        
        folder.createFile(unzipblobs[i]);
      }
    }
  }    
}

После запуска скрипта появится сообщение "Authorization required". Нажимаем кнопку "Continue". В открывшемся окне нажимаем "Accept". Этими действиями говорим гуглу, что мы не против, если наш скрипт получит доступ к данным диска. После разрешения доступа скрипт появится в списке подключенных к диску приложений.

Теперь нужно добиться, чтобы написанный скрипт запускался каждый час. В меню документа выбираем пункт "Resources\Current project's triggers". Добавляем новый триггер с параметрами Run - autounzip, Events - Time-driven, Hour timer, Every hour. Сохраняем триггер. Всё. Осталось дождаться автоматического запуска этого скрипта и проверить результат его работы.

Официальный скринкаст от Google с созданием простенького скрипта можно посмотреть на ютубе.

вторник, 26 августа 2014 г.

Ошибка при запуске screen

Узелок на память.

Последнее время очень часто сталкиваюсь с этой проблемой. При попытке запустить команду screen в консоли linux выскакивает сообщение об ошибке: Cannot open your terminal '/dev/pts/X' - please check. Важно! Перед выполнением screen производилось переключение между пользователями при помощи sudo su - user2.

Исправляется предельно просто. Выполняем в консоли:

script /dev/null

После этой команды screen запускается как нужно.

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

воскресенье, 23 марта 2014 г.

Коллега, будь аккуратен

Передача произвольного числа параметров в функцию

Есть в PHP удобная, на первый взгляд, возможность - функции с переменным числом параметров. При вызове такой функции ей в качестве параметров через запятую передаётся произвольное количество значений. При этом внутренняя логика вызываемой функции содержит специальный код, который позволяет определить, сколько параметров ей передали, и что именно в них содержится. Делается это совсем не сложно с помощью стандартной библиотеки PHP.

Вот, например, функция, которая все передаваемые ей параметры выводит в столбик на экран.

function printVars() {
    $argsCount = func_num_args();
    $argsArray = func_get_args();
    for ($i = 0; $i < $argsCount; $i++) {
        echo $argsArray[$i] . "\n";
    }
} 

printVars('value 1', 'value 2', 'value 3');

Результат:

   value 1
   value 2
   value 3

Всю специальную логику обеспечивают два обращения к стандартной библиотеке: func_num_args для определения количества переданных параметров и func_get_args для доступа к этим параметрам.

Всё в этом коде хорошо, пока параметры мы передаём явно, но давайте представим такую, достаточно стандартную, ситуацию. Мы получили из базы данных какую-то запись, преобразовали её в одномерный массив и хотим передать элементы массива в функцию printVars. Сколько будет элементов массива, мы заранее не знаем. Вопрос на засыпку - как вызвать нашу функцию?

$valuesArray = array('value 1', 'value 2', 'value 3');
printVars(/* ??? */);

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

$quotesValuesArray = array_map(
    function ($item) { return "'$item'"; }, 
    $valuesArray);
eval("printVars(" . implode(', ', $quotesValuesArray) . ");");

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

Интереснее выглядит использование функции call_user_func_array. Она умеет вызывать некую пользовательскую функцию и передавать ей в качестве параметров значения из массива.

call_user_func_array("printVars", $valuesArray);

Код выглядит не идеально, но явно нагляднее, чем первый вариант. Да и мест для потенциальных ошибок стало меньше.

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

class Example {
    public function printVars() {
        // ...
    }
}
// ...
$example = new Example();
call_user_func_array(array($example, "printVars"), $valuesArray);

Проведём ещё один эксперимент. Изменим функцию printVars, чтобы она изменяла и возвращала передаваемые ей параметры.

function printVars() {
    $argsArray = func_get_args();

    if (is_array($argsArray)) {
        foreach ($argsArray as $key => $arg) {
            $argsArray[$key] = $arg . '!';
        }
    }
}

$value1 = 'value 1';
$value2 = 'value 2';
$value3 = 'value 3';
$valuesArray = array(&$value1, &$value2, &$value3);

call_user_func_array("printVars", $valuesArray);

var_dump($valuesArray);

Получаем:

array(3) {
[0]=>
&string(7) "value 1"
[1]=>
&string(7) "value 2"
[2]=>
&string(7) "value 3"
}

Хм. Но это же совсем не то, что мы хотели получить! Параметры $value1, $value2 и $value3 не изменились. Оказывается, с помощью func_get_args нельзя получить ссылки на исходные параметры функции. Решается эта проблема с помощью злого хака.

function printVars() {
    $trace = debug_backtrace();
    $argsArray = $trace[1]['args'][1];

    if (is_array($argsArray)) {
        foreach ($argsArray as $key => $arg) {
            $argsArray[$key] = $arg . '!';
        }
    }

}

$value1 = 'value 1';
$value2 = 'value 2';
$value3 = 'value 3';
$valuesArray = array(&$value1, &$value2, &$value3);

call_user_func_array("printVars", $valuesArray);

var_dump($valuesArray);

Вот теперь мы добились нужного результата.

array(3) {
[0]=>
&string(8) "value 1!"
[1]=>
&string(8) "value 2!"
[2]=>
&string(8) "value 3!"
}

Но и это ещё не все проблемы. call_user_func_array возвращает "the function result, or false on error". А если наша функция возвращает false в качестве результата своей работы? Как нам определить, возникала ошибка или нет?

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

$handler = "printVars";
if (is_callable($handler)) { 
    $res = call_user_func_array( $handler , $valuesArray );
} else {
    throw new Exception();
}

Теперь мы хотя бы знаем, доступна ли нам вызываемая функция. Может быть, не стоит и пытаться её вызывать.

Третьим вариантом вызова функции с переменным числом параметров является механизм reflection. (Ума не приложу, как корректно перевести термин reflection на русский язык.)
function printVars() {
    $argsCount = func_num_args();
    $argsArray = func_get_args();
    for ($i = 0; $i < $argsCount; $i++) {
        echo $argsArray[$i] . "\n";
    }
}

$valuesArray = array('value 1', 'value 2', 'value 3');

$function_ref = new ReflectionFunction('printVars');
$res = $function_ref->invokeArgs($valuesArray);

Этот код имеет ту же проблему с передачей параметров по ссылке, что и вариант с call_user_func_array, но с возвращением "false" в качестве результата выполнения функции всё хорошо. invokeArgs всегда возвращает результат вызываемой функции, в случае же возникновения ошибки бросается исключение ReflectionException.

А что со скоростью выполнения? Усреднённые результаты двадцати прогонов по 10 000 вызовов одной и той же функции с переменным числом параметров:

  • eval - 0.113432610035 мкс
  • call_user_func_array - 0.0677324056625 мкс
  • reflection - 0.063702750206 мкс

Тесты проводились на PHP 5.3.10. Учитывая сомнительное качество тестового стенда, разностью в скорости работы call_user_func_array и reflection можно смело пренебречь, а вот eval и тут проявил себя хуже всех.

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

вторник, 13 августа 2013 г.

Тонкости именования

Когда только начинаешь писать код, то кажется, что самое важное в работе это новомодные технологии, шаблоны проектирования, наследование объектов и прочие полиморфизмы. Что стоит использовать какую-то магическую инверсию зависимости и код сразу станет близок к идеальному. На всякие мелочи, типа стандартов оформления, именования переменных, комментирования методов, отвлекаться не хочется, да и банально жалко терять драгоценное время, которое можно потратить на написание крутой иерархии классов или реализацию какого-нибудь красивого всплывающего окошка. К тому же окошком можно похвастаться перед друзьями, а логичным названием переменной хвастаться как-то нелепо. "Смотрите, смотрите, переменную, в которой хранится, количество заказов, я назвал ordersCount!"

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

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

1. При перечислении объектов в цикле текущий объект всегда называется "oneObject"

Вот пример кода, который я писал раньше:

foreach ($orders as $order) {
    var_dump($order);
}

Проблема в том, что имена переменных, отличающиеся только одной буквой, слишком похожи друг на друга. Из-за этого возникают трудности при чтении такого кода. Кроме того, это потенциальное место для внесения ошибок. Перепутать переменные order и orders ничего не стоит. Теперь я пишу так:

foreach ($orders as $oneOrder) {
    var_dump($oneOrder);
}

Различие между переменными $orders и $oneOrder стали настолько существенными, что нельзя представить ситуацию, когда кто-нибудь случайно их перепутает. Также хорошей альтернативой oneObject может быть использование префикса current. Например, для нашего примера, текущий заказ в цикле можно назвать - currentOrder.

Мой выбор в пользу "one" основывался на том, что в функциях, с которыми мне приходится работать, префикс "current" часто используется для переменных вне циклов. Поэтому для меня oneObject имеет более определённый смысл, чем currentObject.

Upd. Ещё один вариант именования переменной внутри цикла подсмотрел в книжке у Кента Бека. Он предлагает использовать просто $each для небольших циклов и $each<Object> для более крупных. Например, $eachRow или $eachOrder.

2. Функция получения списка объектов называется getObjects, функция получения одного объекта - detailObject

Если используем getObjects и getObject, то опять же имеем проблемы с похожестью имён. Нужно правило именования, которое не позволяет вызвать функцию по ошибке. getObjects и detailObject решают проблему. Неплохим выбором может быть и выбор пар getObject и getObjectList или getObject и listObjects. Отдать предпочтение можно любому из вариантов. Главное условие - чтобы принятый вариант последовательно использовался во всём коде.

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

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

$orderIds = array(1, 2, 3, 4, 5);
$order{???} = implode(',', $orderIds);
buildOrdersQuery($order{???});

Как назвать переменную - результат выполнения функции implode? Для данной переменной то, что она строкового типа, является значимым фактом, Именно для преобразования массива в строку она и была заведена. Нет ничего плохого, чтобы отразить этот факт в её имени,

$orderIds = array(1, 2, 3, 4, 5);
$orderIdsAsString = implode(',', $orderIds);
buildOrdersQuery($orderIdsAsString);

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

4. Булевские переменные называем с использованием префиксов не допускающих неоднозначного толкования хранящихся в них значений

Удобство следованию правилу "булевские переменные называем с использованием префикса is" доказано временем. Из названия переменной сразу понятно, что означает то или иное значение. Но кроме отличного слова "is" есть и другие слова, использование которых также не допускает неоднозначности в толковании значения. Например, has или allow. Не нужно ими пренебрегать

$clientHasDiscount = TRUE;
...
if ($user->allowAccessToCP) { ...

Нельзя предположить, что, если в переменной $clientHasDiscount хранится FALSE, то у клиента есть скидка. Скидка только в том случае, если $clientHasDiscount станет равно TRUE. Название переменной "самодокументировало" своё использование.

5. Если функция возвращает статус выполнения, то переменную, хранящую этот результата надо называть не $res, $result или $status, а $success или $done

Причины те же, что и в предыдущем правиле. Уже из название становится понятен смысл допустимых значений.

public function updateOrder(Order $order) {
    $success = FALSE;
    if ($options->allowOrderUpdate) {
        $success = Db::saveOrder($order);
    }
    return $success;
}

Абсолютно ясно, в каком случае функция отработала нормально, а в каком обновление не состоялось или прошло с ошибками.

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