Постове 1 - 5 от 19 с таг Coding

Sep 17

Когато за пръв път видях Regular Expressions, ми се сториха нещо много яко и митично. И затова отделих доста време за да ги науча. Но в последствие се оказаха не толкова често използвано от мен средство. Най-често те са ми бързото решение, което в последствие се заменя с нещо по-добро. Но въпреки това ги използвам почти всеки ден.

Това за което ги ползвам е най-обикновен search & replace във файл (или проект). Това страшно много ми ускорява работата. Прост пример:

<a href="#">Link 1</a> |
<a href="#">Link 2</a> |
<a href="#">Link 3</a> |
<a href="#">Link 4</a> |
<a href="#">Link 5</a> |
<a href="#">Link 5</a> |
<a href="#">Link 6</a> |

И искаме да стане:

<li><a href="#">Link 1</a></li>
<li><a href="#">Link 2</a></li>
<li><a href="#">Link 3</a></li>
<li><a href="#">Link 4</a></li>
<li><a href="#">Link 5</a></li>
<li><a href="#">Link 5</a></li>
<li><a href="#">Link 6</a></li>

За целта просто заменяме “(<a.*) \|” с “<li>$1</li>

Find: (<a.*) \|
Replace: <li>$1</li>

Като в някой редактори $1 съм го виждал като \1.

Друг “трик”, който ползвах когато писах PHP беше да преименувам даден метод (кой ме би по главата да не ползвам някое IDE е друга тема):

// от това
$foo->someMethod($foo1, "foo2", 'foo3');
$bar->someMethod($bar1, "bar2", 'bar3');
// трябва да се получи (забележете разменените аргументи)
$foo->otherAction("foo2", 'foo3', $foo1);
$bar->otherAction("bar2", 'bar3', $bar1);

За целта просто:

Find: ->someMethod\(([^,]*), ([^,]*), ([^,]*)\)
Replace: ->otherAction($2, $3, $1)

Като този пример може много да се деформира и много зависи от името на метода и дали няма други методи с това име във файла. И е добре винаги да имате тестове който да потвърждават че всичкия код който сте променили работи.

Много често ползвам  Regular Expressions по няколко пъти докато направя каквото ми трябва и го комбинирам със някои Textmate фунции (като ако сте под windows/linux препоръчвам E Texteditor). Естествено гледам да не прекалявам с това. Защото в един момент може и да не си заслужава мисленето на някакъв  сложен pattern. След като добрия стар ръчен find & replace би свършил работа.

Ако някой има въпроси по горните примери или ако има съвети и мнение по въпроса ще се радвам да ги чуя.

Mar 14

Наскоро се наложи да поработя по един доста стар PHP проект, по който не бях работил от години. И там видях нещо, което ведна опреличих като code smell (Всъщност видях много такива неща, но само на това ще обърна внимание).

$cart = CartManager::getCurrentCart();
$cart->setClientInfo('atype',    $user['account_type']);
$cart->setClientInfo('fname',    $user['fname']);
$cart->setClientInfo('lname',    $user['lname']);
$cart->setClientInfo('street',   $user['street']);
$cart->setClientInfo('postnum',  $user['postcode']);
$cart->setClientInfo('epost',    $user['mail']);
$cart->setClientInfo('city',     $user['city']);
$cart->setClientInfo('company',  $user['company']);
$cart->setClientInfo('orgnum',   $user['orgnum']);
$cart->setClientInfo('phone',    $user['phone']);
$cart->setClientInfo('country',  $user['country']);

Което ако идвате от  Java света може и да ви изглежда нормално, но мен ме дразни.

На първо време направих метода setClientInfo да връща $this зада може да използвам прост method chaining. И кода стана така:

CartManager::getCurrentCart()
    ->setClientInfo('atype',    $user['account_type'])
    ->setClientInfo('fname',    $user['fname'])
    ->setClientInfo('lname',    $user['lname'])
    ->setClientInfo('street',   $user['street'])
    ->setClientInfo('postnum',  $user['postcode'])
    ->setClientInfo('epost',    $user['mail'])
    ->setClientInfo('city',     $user['city'])
    ->setClientInfo('company',  $user['company'])
    ->setClientInfo('orgnum',   $user['orgnum'])
    ->setClientInfo('phone',    $user['phone'])
    ->setClientInfo('country',  $user['country']);

Малко по-добре, но пак ми изглеждаше не естествено. Затова просто добавих възможността да може да се предава масив като аргумент. И така стана доста добре:

CartManager::getCurrentCart()->setClientInfo(array(
    'atype',    => $user['account_type'],
    'fname',    => $user['fname'],
    'lname',    => $user['lname'],
    'street',   => $user['street'],
    'postnum',  => $user['postcode'],
    'epost',    => $user['mail'],
    'city',     => $user['city'],
    'company',  => $user['company'],
    'orgnum',   => $user['orgnum'],
    'phone',    => $user['phone'],
    'country',  => $user['country']
));

За нещастие в този проект, нямаше как да направя кода стане още по-чист, без да се налага пренаписване на голяма част от проекта:

CartManager::getCurrentCart()->setClient($user);
Nov 29

От много време не ми харесва как работя със key събития в javascript. Общо взето в по-голямата част от случаите  съм писал нещо такова:

document.observe('keyup', function(e){
	switch(e.keyCode || e.charCode){
		case Event.KEY_UP:		up();		break;
		case Event.KEY_DOWN:	down();		break;
		case Event.KEY_RIGHT:	right();	break;
		case Event.KEY_LEFT:	left();		break;
		case Event.KEY_SPACE:	fire();		break;
	}
});

Което е меко казано досадно и затова реших да направя нещо по въпроса. Реших да използвам custom събитията на Prototype.js, на които ставам все по-голям фен в последно време. И така, взех нормалните key събития – keyup / keydown / keyup и към тях добавих натиснатия бутон. Като за сега се поддържат – backspace, tab, return, esc, left, up, right, down, delete, home, end, pageup, pagedown, insert. И така горния код става така:

document.observe('keyup:up',	up);
document.observe('keyup:down',	down);
document.observe('keyup:right',	right);
document.observe('keyup:left',	left);
document.observe('keyup:space',	fire);

Кey custom събитията може да намерите тук. Ето и 2 малки демонстрации на това как работят – test1 и test2. Също така и в моя javascript playground в github ги има.

За момента само на Firefox и Safari съм ги пробвал, защото за тях ми трябваха. Но не виждам причина да не работят и под други браузъри.

Ако някои има идеи и предложения как това може да се подобри, ще се радвам да ги чуя :)

п.п. Докато работех с keypress, разбрах че имало проблем със keypress в Safari, за повече информация – http://ejohn.org/blog/keypress-in-safari-31/

Sep 23

От началото на месеца, голяма част от работата ми включваше почти изцяло пренаписване на повечето unit тестове на основните ми PHP библиотеки. Основната причина за това беше че повечето тестове бяха на повече от година, като през този период само съм добавял код за нови методи. И през това време тестове станах доста големи и тежки затова се наложи това малко “освежаване”

Основното нещо което исках да тествам по-добре този път бяха ‘protected’ методите на голяма част от класовете. Примерно основния ми Controller клас има само 2 публични метода и всичко друго е protected. Защото идеята на този клас  е да се ползва “отвътре”.

Преди какво правех? Просто си дефинирах един помощен decorator клас през който виках protected методите. Обаче това винаги ми се е струвало доста “мръсно” решение.  Затова реших потърся дали някой не се е сетил нещо по-добро от мен. И затова потърсих -  “php testing protected methods“. И от там намерих този пост – Testing protected Methods in Unit Tests от Frontalaufprall. Което на този етап ми се вижда перфектното решение. И така с много малко промени се получи този код с който тестването на protected методи става изключително лесно:

function getProtectedMethodsProxy($className){
	$proxyClassName = $className . 'PrivateMethodsProxy';

	if (!class_exists($proxyClassName, false)){
		eval('
			class ' . $proxyClassName . ' extends ' . $className . ' {
				public function __call($method, $arguments){
					$method = str_replace("_", "", $method);

					if (!method_exists($this, $method)){
						return parent::__call($method, $arguments);
					}

					return call_user_func_array(array($this, $method),  $arguments);
				}
			}
		');
	}

	if (func_num_args() == 1){
		return new $proxyClassName();
	}

	$class = new ReflectionClass($proxyClassName);
	return $class->newInstanceArgs(array_slice(func_get_args(), 1));
}

Начин на ползване:

$class = getProtectedMethodsProxy('Controller_Action');
$class->_redirect('http://next.pixeldepo.com');
// и вариант с параметри в конструктора
$class = getProtectedMethodsProxy('SomeClass', 1, 2, 3); // new SomeClass(1, 2, 3);
$class->_someVeryProtectedMethod('test');

Все пак eval не винаги е толкова лош :)

Aug 24

В последните ми проекти почти не използвам event handler-и, който да закачам за даден елемент. Вместо това използвам event delegation и от там само слушам за събития, които се случват на долу по DOM дървото. В един по-предишен пост описвам как делегирам submit. Но за нещастие има и други проблемни събития, които не слушат спецификация зa HTML събития – а именно focus / blur.

Рядко използвам focus / blur или когато ги използвам, не ги делегирам. Но преди 3-4 седмици един познат се допита до мен. Защото имаше страница с повече от 150 input елемента, като на всеки от тях добавяше прилично количество event handler-и. Като се сложи и това, че динамично се добавяха и изтриваха доста елементи, нещата не бяха никак розови.

Като за начало всички handler-и се насочиха към document елемента, и това доста ускори нещата. Обаче focus / blur все още изискваше да се слагат отделно на всеки. Но тогава дойде на помощ хака за делегиране на focus/blur и custom събитията на Prototype.js и от тези две неща се роди това доста елегантно решение ( има го и на gist ):

(function() {
    function focusInHandler(e){
        Еvent.element(e).fire("focus:in");
    }
    function focusOutHandler(e){
        Еvent.element(e).fire("focus:out");
    }

    if (document.addEventListener){
        document.addEventListener("focus", focusInHandler, true);
        document.addEventListener("blur", focusOutHandler, true);
    } else {
        document.observe("focusin", focusInHandler);
        document.observe("focusout", focusOutHandler);
    }
})();

Интересното е, че от 1-2 седмици ми се налага да използвам доста често новите focus:in / focus:out , което от една страна доста изчиства и ускорява кода ми. Но и от друга страна прави доста по userfriendly самите компоненти които правя, така че всички са доволни. Като даже ако имам време мисля да пренапиша и кода за делегиране на  submit. :)