PHP для бабусі: вчимося писати читабельний код

13 Березня 2018

наступна стаття
Олександр Гоцалюк

Back End Developer

Олександр Гоцалюк
PHP для бабусі: вчимося писати читабельний код

Ця стаття не про каліграфію та шрифти. А про те, чому потрібно бути акуратним в своєму коді і до чого це призведе.

Якісний, читабельний код це не тільки «красива» особливість, а безпосередній результат роботи розробника. Він відкриває багато переваг, особливо при роботі з командою. Не варто вірити омані про неважливість краси написання PHP коду, навіть за умови виконання функціональних вимог.

Саме тому, ми виділили кілька основних переваг якісного коду:

  • Простота в підтримці;

  • Доступно для читання кожному учаснику команди;

  • Зручно при повторному використанні;

  • Легко шукати і виправляти помилки;

  • Використання відомих патернів;

  • Підвищує вашу цінність як розробника;

  • Вітається технічним роботодавцем;

  • Зменшення ризиків перешкодити бізнесу;

Розберемо детальніше ці пункти. Щоб полегшити роботу в команді собі і іншим, для повторного використання коду, ви повинні з самого початку турбуватись про якість написаного. Якщо запитаєте себе: «Чи буде хтось читати мій код?», то, незалежно від відповіді, його потрібно писати якісно. Оскільки проекти можуть підтримуватися роками, бувають випадки коли через пів року розробники не можуть розібратися в написаному ними ж коді.

У великих проектах або командах важливу роль в якості проекту відіграють коментарі в коді. Суть в тому, щоб дати зрозуміти іншому розробнику з чим і як використовувати ту чи іншу функцію коду. Зазвичай, коментарі розподіляються на багаторядкові і однорядкові. Схема правильного коментаря виглядає так: клас -> метод -> код всередині методу. Правильний коментар спростить життя тому, в чиї руки попався ваш коду.

У разі, коли Ви ділите свої проекти на «можна писати як завгодно» і «потрібен високоякісний код», то завжди залишається ризик сплутати другу групу з першою. Написання коду в одному стилі значно прискорить роботу.

Якщо ж ви пишете код не на асемблері або в двійковому коді, то висновок з цього, що ваш PHP створений для того, щоб бути зрозумілим для людей. Тобто, створений код на «нових» програмах верстки завжди повинен бути читабельним.

Через низьку якість коду деякі додатки важко підтримувати і вони, в прямому сенсі, в безладі. Це призводить до виходу повільних, зі складним у впровадженні функціоналу продуктів, які часто містять помилки. І, що найстрашніше, такі речі доходять до кінцевого користувача. Компанії, які використовують аутсорс розробку програмного забезпечення з метою здешевлення робочої сили, добре знайомі з цією проблемою. Інвестори, у свою чергу, обережніше вкладають грошові ресурси в розробку продуктів. Так народжуються провальні бізнес-кейси.

Навіть при пошуку нової роботи, якісний код може підвищити ваші шанси отримати солідну посаду. За умови, що ваш майбутній роботодавець технічно освічений або теж програміст. Запевняю, ваш код буде ретельно вивчений і викинутий в чорний список, якщо буде написаний «непрофесійно». А в цьому випадку врятує тільки везіння, або зазначена графа «розробник-початківець». Якщо для вас важлива кар'єра, то слід з самого початку інвестувати час і ресурси в вивчення «красивого» коду.

Розглянемо приклади

Здавалося б, написання if оператора не повинно викликати проблем, оскільки він використовується практично скрізь. Часто у програмістів-початківців через неуважність або інші причини зникає порівняння (==), а з'являється привласнення if (=). В результаті, код буде виконується вічно.

if ($_REQUEST['action'] = 'delete') {  
// run the delete code…
}

Відповідно, він повинен виглядати так:

if ($_REQUEST['action'] == 'delete') {  
// run the delete code…
}
Але краще — поміняти місцями змінні в умові:
if ('delete' == $_REQUEST['action']) {  
// run the delete code…
}

Такий підхід захистить від випадкового використання «=» замість «==». Як і в РНР буде повертатися помилка при спробі привласнення значення в рядок.

Без повторень

Напевно, найпопулярніший принцип програмування це «Ніколи не повторюйся». Якщо доводиться писати однаковий код по кілька разів, потрібно огорнути його в функцію або окремий клас, після чого використовувати для уникнення повторів. Хороший приклад, коли є два різних класи, що частково виконують однаковий функціонал. Для запобігання повторів створюємо абстрактний клас, який наслідується цими основними класами і реалізовує загальний функціонал.

Початковий код:

class Foo
{
	private $settings;

    	public function __construct( $settings )
{
$this->setSettings( $settings );
// … some foo code
    	}
	
	protected function setSettings( $settings )
{
        if ( ! is_array( $settings ) ) {
            throw new \Exception( 'Invalid settings' );
        }
        
        $this->settings = $settings;
}

public function work()
{
	// … some foo code
	}
}


class Bar
{
	private $settings;

    	public function __construct( $settings )
{
$this->setSettings( $settings );
// … some bar code
    	}
	
	protected function setSettings( $settings )
{
        if ( ! is_array( $settings ) ) {
            throw new \Exception( 'Invalid settings' );
        }
        
        $this->settings = $settings;
}

public function work()
{
	// … some bar code
	}
}

Перетворимо:

abstract class Base
{
	private $settings;

	protected function setSettings( $settings )
{
        if ( ! is_array( $settings ) ) {
            throw new \Exception( 'Invalid settings' );
        }
        
        $this->settings = $settings;
}
}

class Foo extends Base
{
	public function __construct( $settings )
{
$this->setSettings( $settings );
// … some foo code
    	}


public function work()
{
		// … some foo code
	}
}


class Bar extends Base
{
	public function __construct( $settings )
{
$this->setSettings( $settings );
// … some bar code
    	}
	
public function work()
{
	// … some bar code
	}
}

Розподіл комплексних функцій

Ще одна проблема, яка може призвести до ускладнень в розробці і підтримці проекту, це код, що важко читається, в комплексних функціях або методах. Дуже важливо робити свій код зрозумілим і читабельним для інших людей, особливо при роботі в команді. Але є рішення: розбити комплексні блоки коду PHP на менші логічні блоки. Нижче наведений приклад комплексної функції. Не турбуйтеся про розуміння всього в ній, просто зверніть увагу наскільки складно все виглядає.

function getSettings() 
{
    if ($this->hasParameter('settings')) {
        $settings = $this->getParameter('settings');
    } else if ($this->hasParameter('setting')) {
        $settings = [ $this->getParameter('setting') ];
    }

    if (isset($settings)) {
        $root = $this->getParameter('root');
        if (!$root) {
            if (isset($_SERVER['PWD'])) {
                $root = $_SERVER['PWD'];
            } else if (isset($_SERVER['DOCUMENT_ROOT'])) {
                $root = $_SERVER['DOCUMENT_ROOT'];
            }
        }

        $settings['root'] = $root;
    }

    return $settings;
}

Набагато простіше і зрозуміліше для читання метод нижче:

function getBaseSettings()
{
		if ($this->hasParameter('settings')) {
        		return $this->getParameter('settings');
    	} else if ($this->hasParameter('setting')) {
       		return [ $this->getParameter('setting') ];
   	}
	return [];
	}

	function getRootFolder()
{
$root = $this->getParameter('root');
if (!$root) {
            	$root = $_SERVER['PWD'];
            }

	if (!$root) {
                $root = $_SERVER['DOCUMENT_ROOT'];
            }

	return $root;
}

function getSettings() 
{
	$settings = $this->getBaseSettings();
	if ($settings) {
		$settings['root'] = $this->getRootFolder();
	}

	return $settings;
}

При розбитті комплексних блоків краще використовувати спрощення умовних виразів. Часто може зустрічатися така умова:

if (isset($data['action']) && is_string($data['action']) && in_array($data['action'], ['add', 'get', 'delete'])) {
	echo 'very nice';
}

Але якщо винести умову в окрему функцію і дати їй відповідну назву, це дасть нам кілька переваг.

function isValid($data)
	{
		return isset($data['action']) && is_string($data['action']) && in_array($data['action'], ['add', 'get', 'delete']);
	}

	if (isValid($data)) {
		echo 'very nice';
}

Код стає менш нагромадженим. По-друге, по назві функції, що використовується в умові, можна зрозуміти, для чого вона призначена і вже не вдаватись в її деталі без потреби.

Ще однією хорошою практикою є спрощення комплексності в коді — уникати множинної вкладеності логічних блоків.

function getSetting ($key, $default = '')
	{
		$settings = $this->getSettings();
		
		if ($key) {
			if (is_array($key)) {
				return $this->filterSetting($key);
			} else {
				if ( 'prefix' == $key && !isset($settings['prefix']) {
					return $this->getDefaultPrefix();
				} else {
					if (is_string($key) && isset($settings[$key]) {
						return $settings[$key];	
					}
					return $default;
				}
			}
		} else {
			return $this->getErrorSetting($key);
		}
	}

При подальшому ускладненні ця функція може перерости в повне «пекло» з умов. Для уникнення цього, вкладені умови замінюють граничними операторами:

function getSetting ($key, $default = '')
	{
		if (boolval($key) === false)
			return $this->getErrorSetting($key);

		if (is_array($key))
return $this->filterSetting($key);
		$settings = $this->getSettings();
		
if ( 'prefix' == $key && !isset($settings['prefix'])
			return $this->getDefaultPrefix();

if (is_string($key) && isset($settings[$key])
return $settings[$key];	
					
		return $default;
	}


PHP досить немолода мова програмування, за весь час її використання сформувалося велика спільнота розробників. Вони реалізовували хороші і погані практики, з застосувань яких можна виділити багато прикладів і наслідків ведення проекту.

У статті прописані рекомендації щодо вирішення найбільш поширених помилок в коді. З деякими прикладами ви можете не погоджуватися, але краще завжди бути готовим до наслідків, які приховує «поганий» код.

Пиши код так, щоб його змогла зрозуміти твоя бабуся або прогер - психопат-вбивця, який знає де ти живеш :))


Схожі статті
Записатись на консультацію

Ми зв'яжемось з Вами протягом 10 хвилин