Awesome
<div dir="rtl">Clean Code در PHP
فهرست
- مقدمه
- متغیرها
- از نام های متغیر معنی دار و قابل تلفظ استفاده کنید
- از همان واژگان برای همان نوع متغیر استفاده کنید
- از نام های قابل جستجو استفاده کنید (بخش 1)
- از نام های قابل جستجو استفاده کنید (بخش 2)
- از متغیرهای توضیحی استفاده کنید
- خودداری از ساختارهای تودرتو (بخش 1)
- خودداری از ساختارهای تودرتو (بخش 2)
- از نگاشت ذهنی خودداری کنید
- نیاز به تکرار نام شی در متغیرها نیست
- از آرگومان های پیش فرض به جای اتصال کوتاه یا شرطی استفاده کنید
- ساختارهای مقایسه ای
- توابع
- آرگومان های توابع (حالت ایده آل 2 آرگومان یا کمتر)
- نام تابع باید بگوید که چه کاری انجام می دهد
- توابع فقط باید شامل یک سطح انتزاع باشند
- از flagها به عنوان پارامترهای توابع استفاده نکنید
- از عوارض جانبی یا Side Effects خودداری کنید
- از نوشتن توابع عمومی جلوگیری کنید
- از Singleton pattern استفاده نکنید
- دستورات شرطی را کپسوله سازی کنید
- از استفاده ی شروط منفی جلوگیری کنید
- از شرطی شدن اجتناب کنید
- از بررسی نوع خودداری کنید (بخش 1)
- از بررسی نوع خودداری کنید (بخش 2)
- حذف کد مرده
- اشیا و ساختارهای داده
- کلاس ها
- SOLID
- اصل DRY
- ترجمه ها
مقدمه
اصول مهندسی نرم افزار ، از کتاب Robert Code Martin Clean Code ، اقتباس گرفته شده است تا در این نوشته اصول آن برای زبان برنامه نویسی PHP را بررسی کنیم. این یک راهنمای ساده نیست. این یک راهنما برای تولید نرم افزارهای قابل خواندن با خوانایی بالا، قابل استفاده مجدد در زبان برنامه نویسی پی اچ پی است.
لازم نیست که هر اصلی که گفته شد دقیقاً رعایت شود، و حتی تعداد کمتر مورد توافق همه توسعه دهدگان قرار خواهند گرفت. اینها رهنمودهایی هستند و چیز دیگری فراتر از این نیستند ، اما مواردی هستند که در طول چندین سال تجربه جمعی توسط نویسندگان Clean Code نوشته شده است.
Inspired from clean-code-javascript.
اگرچه بسیاری از توسعه دهندگان هنوز از PHP 5 استفاده می کنند ، اما بیشتر نمونه های این مقاله فقط با PHP 7.1+ کار می کنند.
متغیرها
از نام های متغیر معنی دار و قابل تلفظ استفاده کنید:
بد:
</div>declare(strict_types=1);
$ymdstr = $moment->format('y-m-d');
<div dir="rtl">
خوب:
</div>declare(strict_types=1);
$currentDate = $moment->format('y-m-d');
<div dir="rtl">
از همان واژگان برای همان نوع متغیر استفاده کنید:
بد:
</div>declare(strict_types=1);
getUserInfo();
getUserData();
getUserRecord();
getUserProfile();
<div dir="rtl">
خوب:
</div>declare(strict_types=1);
getUser();
<div dir="rtl">
از نام های قابل جستجو استفاده کنید (بخش 1)
ما بیشتر از آنچه را که می نویسیم کدها را می خوانیم. مهم است که کدی که می نویسیم قابل خواندن و قابل جستجو باشد. با انتخاب نام برای متغیرهایی که در نهایت برای درک برنامه ما معنی دار تر هستند کدهای خود را ساده تر می کنیم. نام های متغیرهای خود را در برنامه ی مورد نظرتان قابل جستجو انتخاب کنید.
بد:
</div>declare(strict_types=1);
// What the heck is 448 for?
$result = $serializer->serialize($data, 448);
<div dir="rtl">
خوب:
</div>declare(strict_types=1);
$json = $serializer->serialize($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
<div dir="rtl">
از نام های قابل جستجو استفاده کنید (بخش 2)
بد:
</div>declare(strict_types=1);
class User
{
// What the heck is 7 for?
public $access = 7;
}
// What the heck is 4 for?
if ($user->access & 4) {
// ...
}
// What's going on here?
$user->access ^= 2;
<div dir="rtl">
خوب:
</div>declare(strict_types=1);
class User
{
public const ACCESS_READ = 1;
public const ACCESS_CREATE = 2;
public const ACCESS_UPDATE = 4;
public const ACCESS_DELETE = 8;
// User as default can read, create and update something
public $access = self::ACCESS_READ | self::ACCESS_CREATE | self::ACCESS_UPDATE;
}
if ($user->access & User::ACCESS_UPDATE) {
// do edit ...
}
// Deny access rights to create something
$user->access ^= User::ACCESS_CREATE;
<div dir="rtl">
از متغیرهای توضیحی استفاده کنید
بد:
</div>declare(strict_types=1);
$address = 'One Infinite Loop, Cupertino 95014';
$cityZipCodeRegex = '/^[^,]+,\s*(.+?)\s*(\d{5})$/';
preg_match($cityZipCodeRegex, $address, $matches);
saveCityZipCode($matches[1], $matches[2]);
<div dir="rtl">
بدک نیست:
این حالت از حالت بالایی بهتر است، اما هنوز به شدت به regex وابسته هستیم.
</div>declare(strict_types=1);
$address = 'One Infinite Loop, Cupertino 95014';
$cityZipCodeRegex = '/^[^,]+,\s*(.+?)\s*(\d{5})$/';
preg_match($cityZipCodeRegex, $address, $matches);
[, $city, $zipCode] = $matches;
saveCityZipCode($city, $zipCode);
<div dir="rtl">
خوب:
با نامگذاری الگوهای فرعی وابستگی به regex را کاهش می دهیم.
</div>declare(strict_types=1);
$address = 'One Infinite Loop, Cupertino 95014';
$cityZipCodeRegex = '/^[^,]+,\s*(?<city>.+?)\s*(?<zipCode>\d{5})$/';
preg_match($cityZipCodeRegex, $address, $matches);
saveCityZipCode($matches['city'], $matches['zipCode']);
<div dir="rtl">
خودداری از ساختارهای تودرتو (بخش 1)
با if-else های زیاد و طولانی و تو در تو خواندن کد شما دشوار می شود. برای این کار از متغیرهای صریح استفاده کنید.
بد:
</div>declare(strict_types=1);
function isShopOpen($day): bool
{
if ($day) {
if (is_string($day)) {
$day = strtolower($day);
if ($day === 'friday') {
return true;
} elseif ($day === 'saturday') {
return true;
} elseif ($day === 'sunday') {
return true;
}
return false;
}
return false;
}
return false;
}
<div dir="rtl">
خوب:
</div>declare(strict_types=1);
function isShopOpen(string $day): bool
{
if (empty($day)) {
return false;
}
$openingDays = ['friday', 'saturday', 'sunday'];
return in_array(strtolower($day), $openingDays, true);
}
<div dir="rtl">
خودداری از ساختارهای تودرتو (بخش 2)
بد:
</div>declare(strict_types=1);
function fibonacci(int $n)
{
if ($n < 50) {
if ($n !== 0) {
if ($n !== 1) {
return fibonacci($n - 1) + fibonacci($n - 2);
}
return 1;
}
return 0;
}
return 'Not supported';
}
<div dir="rtl">
خوب:
</div>declare(strict_types=1);
function fibonacci(int $n): int
{
if ($n === 0 || $n === 1) {
return $n;
}
if ($n >= 50) {
throw new Exception('Not supported');
}
return fibonacci($n - 1) + fibonacci($n - 2);
}
<div dir="rtl">
از نگاشت ذهنی خودداری کنید
سعی کنید کاری کنید که کسی که کد شما را می خواند نیاز به ترجمه نداشته باشد و به صورت صریع متوجه شود.
بد:
</div>$l = ['Austin', 'New York', 'San Francisco'];
for ($i = 0; $i < count($l); $i++) {
$li = $l[$i];
doStuff();
doSomeOtherStuff();
// ...
// ...
// ...
// Wait, what is `$li` for again?
dispatch($li);
}
<div dir="rtl">
خوب:
</div>declare(strict_types=1);
$locations = ['Austin', 'New York', 'San Francisco'];
foreach ($locations as $location) {
doStuff();
doSomeOtherStuff();
// ...
// ...
// ...
dispatch($location);
}
<div dir="rtl">
نیاز به تکرار نام شی در متغیرها نیست
برای نام گذاری اعضای یک کلاس نیاز به استفاده از نام کلاس در نام آن ها نیست.
بد:
</div>declare(strict_types=1);
class Car
{
public $carMake;
public $carModel;
public $carColor;
//...
}
<div dir="rtl">
خوب:
</div>declare(strict_types=1);
class Car
{
public $make;
public $model;
public $color;
//...
}
<div dir="rtl">
از آرگومان های پیش فرض به جای اتصال کوتاه یا شرطی استفاده کنید
خیلی خوب نیست:
استفاده از $breweryName
مناسب نیست زیرا می تواند NULL
باشد.
function createMicrobrewery($breweryName = 'Hipster Brew Co.'): void
{
// ...
}
<div dir="rtl">
بدک نیست:
این حالت بهتر از حالت قبلی است زیرا قابل درک تر است اما ارزش متغیر را نیز می توان بهتر کنترل کرد.
</div>function createMicrobrewery($name = null): void
{
$breweryName = $name ?: 'Hipster Brew Co.';
// ...
}
<div dir="rtl">
خوب:
می توانید از type hinting استفاده کنید و مطمئن باشید که مقدار $breweryName
برابر NULL
نخواهد بود.
function createMicrobrewery(string $breweryName = 'Hipster Brew Co.'): void
{
// ...
}
<div dir="rtl">
ساختارهای مقایسه ای
از مقایسه با نوع یکسان استفاده کنید - identical comparison
خیلی خوب نیست:
در مقایسه ی ساده رشته ها و اعداد رشته به صورت یکسان در نظر گرفته می شود.
</div>declare(strict_types=1);
$a = '42';
$b = 42;
if ($a != $b) {
// The expression will always pass
}
<div dir="rtl">
مقایسه متغیر a و b برابر FALSE
است اما در واقع TRUE
است! رشته 42 با عدد 42 متفاوت است.
خوب:
در این حالت مقایسه هم با نوع متغیر و هم با مقدار آن انجام می شود.
</div>declare(strict_types=1);
$a = '42';
$b = 42;
if ($a !== $b) {
// The expression is verified
}
<div dir="rtl">
مقایسه ی a با b در کد بالا برابر TRUE
خواهد شد.
اپراتورهای اتصال NULL
null coalescing عملگر جدیدی است که در introduced in PHP 7 معرفی شده است. null coalescing را با علامت ??
می نویسیم که اگر درست باشد عملوند اول خود را برمی گرداند در غیر اینصورت عملوند دوم خود را بر می گرداند؛ به طور کلی به جای isset
استفاده می شود.
بد:
</div>declare(strict_types=1);
if (isset($_GET['name'])) {
$name = $_GET['name'];
} elseif (isset($_POST['name'])) {
$name = $_POST['name'];
} else {
$name = 'nobody';
}
<div dir="rtl">
خوب:
</div>declare(strict_types=1);
$name = $_GET['name'] ?? $_POST['name'] ?? 'nobody';
<div dir="rtl">
توابع
آرگومان های توابع (حالت ایده آل 2 آرگومان یا کمتر)
محدود کردن مقدار پارامترهای توابع بسیار مهم است زیرا تست عملکرد کدهای شما آسان تر می شود. داشتن بیش از سه مورد منجر به سختی در عملیات تست و ردگیری خطا می شود.
تعداد صفر ایده آل است؛ بین یک تا دو خوب است؛ بیش از سه مناسب نیست.
اگر بیش از سه آرگومان دارید احتمالا چند کار را در یک کار انجام می دهید که نباید اینگونه باشد و احتمالا استدلال شما از یک شی نادرست است.
بد:
</div>declare(strict_types=1);
class Questionnaire
{
public function __construct(
string $firstname,
string $lastname,
string $patronymic,
string $region,
string $district,
string $city,
string $phone,
string $email
) {
// ...
}
}
<div dir="rtl">
خوب:
</div>declare(strict_types=1);
class Name
{
private $firstname;
private $lastname;
private $patronymic;
public function __construct(string $firstname, string $lastname, string $patronymic)
{
$this->firstname = $firstname;
$this->lastname = $lastname;
$this->patronymic = $patronymic;
}
// getters ...
}
class City
{
private $region;
private $district;
private $city;
public function __construct(string $region, string $district, string $city)
{
$this->region = $region;
$this->district = $district;
$this->city = $city;
}
// getters ...
}
class Contact
{
private $phone;
private $email;
public function __construct(string $phone, string $email)
{
$this->phone = $phone;
$this->email = $email;
}
// getters ...
}
class Questionnaire
{
public function __construct(Name $name, City $city, Contact $contact)
{
// ...
}
}
<div dir="rtl">
نام تابع باید بگوید که چه کاری انجام می دهد
بد:
</div>class Email
{
//...
public function handle(): void
{
mail($this->to, $this->subject, $this->body);
}
}
$message = new Email(...);
// What is this? A handle for the message? Are we writing to a file now?
$message->handle();
<div dir="rtl">
خوب:
</div>class Email
{
//...
public function send(): void
{
mail($this->to, $this->subject, $this->body);
}
}
$message = new Email(...);
// Clear and obvious
$message->send();
<div dir="rtl">
توابع فقط باید شامل یک سطح انتزاع باشند
وقتی بیش از یک سطح انتزاع داشته باشید ، عملکرد شما معمولاً پیچیده تر می شود؛ تقسیم توابع منجر به استفاده مجدد و تست آسان تر می شود.
بد:
</div>declare(strict_types=1);
function parseBetterPHPAlternative(string $code): void
{
$regexes = [
// ...
];
$statements = explode(' ', $code);
$tokens = [];
foreach ($regexes as $regex) {
foreach ($statements as $statement) {
// ...
}
}
$ast = [];
foreach ($tokens as $token) {
// lex...
}
foreach ($ast as $node) {
// parse...
}
}
<div dir="rtl">
خیلی بد نیست:
</div>function tokenize(string $code): array
{
$regexes = [
// ...
];
$statements = explode(' ', $code);
$tokens = [];
foreach ($regexes as $regex) {
foreach ($statements as $statement) {
$tokens[] = /* ... */;
}
}
return $tokens;
}
function lexer(array $tokens): array
{
$ast = [];
foreach ($tokens as $token) {
$ast[] = /* ... */;
}
return $ast;
}
function parseBetterPHPAlternative(string $code): void
{
$tokens = tokenize($code);
$ast = lexer($tokens);
foreach ($ast as $node) {
// parse...
}
}
<div dir="rtl">
خوب:
بهترین راه حل ، حذف وابستگی های تابع parseBetterPHPAlternative()
است.
class Tokenizer
{
public function tokenize(string $code): array
{
$regexes = [
// ...
];
$statements = explode(' ', $code);
$tokens = [];
foreach ($regexes as $regex) {
foreach ($statements as $statement) {
$tokens[] = /* ... */;
}
}
return $tokens;
}
}
class Lexer
{
public function lexify(array $tokens): array
{
$ast = [];
foreach ($tokens as $token) {
$ast[] = /* ... */;
}
return $ast;
}
}
class BetterPHPAlternative
{
private $tokenizer;
private $lexer;
public function __construct(Tokenizer $tokenizer, Lexer $lexer)
{
$this->tokenizer = $tokenizer;
$this->lexer = $lexer;
}
public function parse(string $code): void
{
$tokens = $this->tokenizer->tokenize($code);
$ast = $this->lexer->lexify($tokens);
foreach ($ast as $node) {
// parse...
}
}
}
<div dir="rtl">
از flagها به عنوان پارامترهای توابع استفاده نکنید
flagها به کاربر شما می گویند که این تابع بیش از یک کار انجام می دهد. توابع باید یک کار انجام دهند. اگر توابع شما براساس کدهای boolean دنبال می شوند، توابع خود را تجزیه کنید.
بد:
</div>declare(strict_types=1);
function createFile(string $name, bool $temp = false): void
{
if ($temp) {
touch('./temp/' . $name);
} else {
touch($name);
}
}
<div dir="rtl">
خوب:
</div>declare(strict_types=1);
function createFile(string $name): void
{
touch($name);
}
function createTempFile(string $name): void
{
touch('./temp/' . $name);
}
<div dir="rtl">
از عوارض جانبی یا Side Effects خودداری کنید
به جای اینکه مقدار یک تابع در یک متغیر global نوشته شود به صورت غیر void آنرا تعریف کنید و مقدار را برگردانید.
بد:
</div>declare(strict_types=1);
// Global variable referenced by following function.
// If we had another function that used this name, now it'd be an array and it could break it.
$name = 'Ryan McDermott';
function splitIntoFirstAndLastName(): void
{
global $name;
$name = explode(' ', $name);
}
splitIntoFirstAndLastName();
var_dump($name);
// ['Ryan', 'McDermott'];
<div dir="rtl">
خوب:
</div>declare(strict_types=1);
function splitIntoFirstAndLastName(string $name): array
{
return explode(' ', $name);
}
$name = 'Ryan McDermott';
$newName = splitIntoFirstAndLastName($name);
var_dump($name);
// 'Ryan McDermott';
var_dump($newName);
// ['Ryan', 'McDermott'];
<div dir="rtl">
از نوشتن توابع عمومی جلوگیری کنید.
مفهوم Polluting globals یک عمل بد در بسیاری از زبان های برنامه نویسی می باشد چون شما می توانید با یک کتابخانه یا API دیگری این کار را انجام دهید.
برای مثال: اگر بخواهید یک آرایه ی configuration بسازید چه می کنید؟ شما می توانید یک تابع عمومی config()
بنویسید یا با یک کتابخانه ی دیگر این کار را انجام دهید.
بد:
</div>declare(strict_types=1);
function config(): array
{
return [
'foo' => 'bar',
];
}
<div dir="rtl">
خوب:
</div>declare(strict_types=1);
class Configuration
{
private $configuration = [];
public function __construct(array $configuration)
{
$this->configuration = $configuration;
}
public function get(string $key): ?string
{
// null coalescing operator
return $this->configuration[$key] ?? null;
}
}
<div dir="rtl">
configuration را بارگیری کنید و نمونه از از کلاس Configuration
ایجاد کنید
declare(strict_types=1);
$configuration = new Configuration([
'foo' => 'bar',
]);
<div dir="rtl">
حالا باید یک نمونه از Configuration
استفاده کنید
از Singleton pattern استفاده نکنید
</div>Singleton is an anti-pattern. Paraphrased from Brian Button:
- They are generally used as a global instance, why is that so bad? Because you hide the dependencies of your application in your code, instead of exposing them through the interfaces. Making something global to avoid passing it around is a code smell.
- They violate the single responsibility principle: by virtue of the fact that they control their own creation and lifecycle.
- They inherently cause code to be tightly coupled. This makes faking them out under test rather difficult in many cases.
- They carry state around for the lifetime of the application. Another hit to testing since you can end up with a situation where tests need to be ordered which is a big no for unit tests. Why? Because each unit test should be independent from the other.
There is also very good thoughts by Misko Hevery about the root of problem.
<div dir="rtl">بد:
</div>declare(strict_types=1);
class DBConnection
{
private static $instance;
private function __construct(string $dsn)
{
// ...
}
public static function getInstance(): self
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
// ...
}
$singleton = DBConnection::getInstance();
<div dir="rtl">
خوب:
</div>declare(strict_types=1);
class DBConnection
{
public function __construct(string $dsn)
{
// ...
}
// ...
}
<div dir="rtl">
نمونه ای از کلاس DBConnection ایجاد کرده و آن را با DSN پیکربندی کنید.
</div>declare(strict_types=1);
$connection = new DBConnection($dsn);
<div dir="rtl">
و اکنون باید از نمونه DBConnection
در برنامه خود استفاده کنید.
دستورات شرطی را کپسوله سازی کنید
بد:
</div>declare(strict_types=1);
if ($article->state === 'published') {
// ...
}
<div dir="rtl">
خوب:
</div>declare(strict_types=1);
if ($article->isPublished()) {
// ...
}
<div dir="rtl">
از استفاده ی شروط منفی جلوگیری کنید
بد:
</div>declare(strict_types=1);
function isDOMNodeNotPresent(DOMNode $node): bool
{
// ...
}
if (! isDOMNodeNotPresent($node)) {
// ...
}
<div dir="rtl">
خوب:
</div>declare(strict_types=1);
function isDOMNodePresent(DOMNode $node): bool
{
// ...
}
if (isDOMNodePresent($node)) {
// ...
}
<div dir="rtl">
از شرطی شدن اجتناب کنید
به نظر می رسد این یک کار غیرممکن است. با شنیدن این موضوع احتمالا همه ی برنامه نویسان عزیز می گویند "چگونه قرار است بدون if ، کاری انجام دهیم؟" پاسخ این است که شما می توانید برای رسیدن به همان کار در بسیاری از موارد از چند case استفاده کنید. سوال دوم معمولاً این است ، "خوب این عالی است اما چرا من می خواهم این کار را انجام دهم؟" پاسخ این است که مفهوم قبلی که در ارتباط با clean code خواندیم این است که یک تابع فقط باید یک کار واحد انجام دهد. وقتی کلاس ها و توابعی را دارید که دستور if دارند ، به خواننده ی کد خود می گویید عملکرد این بخش شما بیش از یک کار را انجام می دهد. به یاد داشته باشید ، فقط یک کار را باید انجام دهید.
بد:
</div>declare(strict_types=1);
class Airplane
{
// ...
public function getCruisingAltitude(): int
{
switch ($this->type) {
case '777':
return $this->getMaxAltitude() - $this->getPassengerCount();
case 'Air Force One':
return $this->getMaxAltitude();
case 'Cessna':
return $this->getMaxAltitude() - $this->getFuelExpenditure();
}
}
}
<div dir="rtl">
خوب:
</div>declare(strict_types=1);
interface Airplane
{
// ...
public function getCruisingAltitude(): int;
}
class Boeing777 implements Airplane
{
// ...
public function getCruisingAltitude(): int
{
return $this->getMaxAltitude() - $this->getPassengerCount();
}
}
class AirForceOne implements Airplane
{
// ...
public function getCruisingAltitude(): int
{
return $this->getMaxAltitude();
}
}
class Cessna implements Airplane
{
// ...
public function getCruisingAltitude(): int
{
return $this->getMaxAltitude() - $this->getFuelExpenditure();
}
}
<div dir="rtl">
از بررسی نوع خودداری کنید (بخش 1)
در زبان برنامه نویسی PHP بررسی نوع نداریم اگر برای این موضوع وسواس دارید باید با یک تابع استفاده کنید که بهتر است این کار را انجام ندهید.
بد:
</div>declare(strict_types=1);
function travelToTexas($vehicle): void
{
if ($vehicle instanceof Bicycle) {
$vehicle->pedalTo(new Location('texas'));
} elseif ($vehicle instanceof Car) {
$vehicle->driveTo(new Location('texas'));
}
}
<div dir="rtl">
خوب:
</div>declare(strict_types=1);
function travelToTexas(Vehicle $vehicle): void
{
$vehicle->travelTo(new Location('texas'));
}
<div dir="rtl">
از بررسی نوع خودداری کنید (بخش 2)
اگر با مقادیر ابتدایی اولیه مانند رشته ها ، اعداد صحیح و آرایه ها کار می کنید و از PHP 7+ استفاده می کنید و نمی توانید از polymorphism استفاده کنید اما هنوز هم به بررسی نوع نیاز دارید ، باید اعلام نوع یا حالت سخت را در نظر بگیرید. نوع ایستا در بالای نحو استاندارد PHP را برای شما فراهم می کند. مشکلی که در بررسی دستی نوع وجود دارد این است که انجام آن به بخش اضافی نیاز دارد که type-safety ساختگی شما خوانایی از دست رفته را جبران نمی کند.
کدهای PHP خود را به صورت Clean Code نگه دارید ، تست های خوبی بنویسید و بررسی های خوبی در مورد کد داشته باشید. در غیر این صورت ، همه این کارها با اعلام نوع دقیق PHP یا حالت دقیق انجام دهید.
بد:
</div>declare(strict_types=1);
function combine($val1, $val2): int
{
if (! is_numeric($val1) || ! is_numeric($val2)) {
throw new Exception('Must be of type Number');
}
return $val1 + $val2;
}
<div dir="rtl">
خوب:
</div>declare(strict_types=1);
function combine(int $val1, int $val2): int
{
return $val1 + $val2;
}
<div dir="rtl">
حذف کد مرده
کد مرده به همان اندازه ای که کد تکراری بد است نامناسب است. هیچ دلیلی وجود ندارد که آن را در کدهای خود نگه دارید. اگر کدی فراخوانی نمی شود، خلاص شوید!
بد:
</div>declare(strict_types=1);
function oldRequestModule(string $url): void
{
// ...
}
function newRequestModule(string $url): void
{
// ...
}
$request = newRequestModule($requestUrl);
inventoryTracker('apples', $request, 'www.inventory-awesome.io');
<div dir="rtl">
خوب:
</div>declare(strict_types=1);
function requestModule(string $url): void
{
// ...
}
$request = requestModule($requestUrl);
inventoryTracker('apples', $request, 'www.inventory-awesome.io');
<div dir="rtl">
اشیا و ساختارهای داده
استفاده از کپسوله سازی اشیا
در PHP می توانید کلمات کلیدی public
, protected
, privaten
داریم که با استفاده از آن ، می توانید دسترسی به ویژگی ها را روی یک اشیا کنترل کنید.
- When you want to do more beyond getting an object property, you don't have to look up and change every accessor in your codebase.
- Makes adding validation simple when doing a
set
. - Encapsulates the internal representation.
- Easy to add logging and error handling when getting and setting.
- Inheriting this class, you can override default functionality.
- You can lazy load your object's properties, let's say getting it from a server.
Additionally, this is part of Open/Closed principle.
<div dir="rtl">بد:
</div>declare(strict_types=1);
class BankAccount
{
public $balance = 1000;
}
$bankAccount = new BankAccount();
// Buy shoes...
$bankAccount->balance -= 100;
<div dir="rtl">
خوب:
</div>class BankAccount
{
private $balance;
public function __construct(int $balance = 1000)
{
$this->balance = $balance;
}
public function withdraw(int $amount): void
{
if ($amount > $this->balance) {
throw new \Exception('Amount greater than available balance.');
}
$this->balance -= $amount;
}
public function deposit(int $amount): void
{
$this->balance += $amount;
}
public function getBalance(): int
{
return $this->balance;
}
}
$bankAccount = new BankAccount();
// Buy shoes...
$bankAccount->withdraw($shoesPrice);
// Get balance
$balance = $bankAccount->getBalance();
<div dir="rtl">
اعضای اشیا را با protected, private تعریف کنید
برای اطلاعات بیشتر این نوشته را از Fabien Potencier بخوانید.
بد:
</div>declare(strict_types=1);
class Employee
{
public $name;
public function __construct(string $name)
{
$this->name = $name;
}
}
$employee = new Employee('John Doe');
// Employee name: John Doe
echo 'Employee name: ' . $employee->name;
<div dir="rtl">
خوب:
</div>declare(strict_types=1);
class Employee
{
private $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function getName(): string
{
return $this->name;
}
}
$employee = new Employee('John Doe');
// Employee name: John Doe
echo 'Employee name: ' . $employee->getName();
<div dir="rtl">
کلاس ها
ترکیب را بر ارث بری ترجیح دهید
ابتدا بهتر است کمی در ارتباط با Design Patternsها بخوانید.
بد
</div>declare(strict_types=1);
class Employee
{
private $name;
private $email;
public function __construct(string $name, string $email)
{
$this->name = $name;
$this->email = $email;
}
// ...
}
// Bad because Employees "have" tax data.
// EmployeeTaxData is not a type of Employee
class EmployeeTaxData extends Employee
{
private $ssn;
private $salary;
public function __construct(string $name, string $email, string $ssn, string $salary)
{
parent::__construct($name, $email);
$this->ssn = $ssn;
$this->salary = $salary;
}
// ...
}
<div dir="rtl">
*خوب
</div>declare(strict_types=1);
class EmployeeTaxData
{
private $ssn;
private $salary;
public function __construct(string $ssn, string $salary)
{
$this->ssn = $ssn;
$this->salary = $salary;
}
// ...
}
class Employee
{
private $name;
private $email;
private $taxData;
public function __construct(string $name, string $email)
{
$this->name = $name;
$this->email = $email;
}
public function setTaxData(EmployeeTaxData $taxData): void
{
$this->taxData = $taxData;
}
// ...
}
<div dir="rtl">
از استفاده ی fluent interfaces خودکاری کنید
در ارتباط با Fluent interface ها بخوانید؛ برای بهتر متوجه شدن این موضوع این نوشته را از Marco Pivetta بخوانید.
</div>While there can be some contexts, frequently builder objects, where this pattern reduces the verbosity of the code (for example the PHPUnit Mock Builder or the Doctrine Query Builder), more often it comes at some costs:
- Breaks Encapsulation.
- Breaks Decorators.
- Is harder to mock in a test suite.
- Makes diffs of commits harder to read.
بد:
</div>declare(strict_types=1);
class Car
{
private $make = 'Honda';
private $model = 'Accord';
private $color = 'white';
public function setMake(string $make): self
{
$this->make = $make;
// NOTE: Returning this for chaining
return $this;
}
public function setModel(string $model): self
{
$this->model = $model;
// NOTE: Returning this for chaining
return $this;
}
public function setColor(string $color): self
{
$this->color = $color;
// NOTE: Returning this for chaining
return $this;
}
public function dump(): void
{
var_dump($this->make, $this->model, $this->color);
}
}
$car = (new Car())
->setColor('pink')
->setMake('Ford')
->setModel('F-150')
->dump();
<div dir="rtl">
خوب:
</div>declare(strict_types=1);
class Car
{
private $make = 'Honda';
private $model = 'Accord';
private $color = 'white';
public function setMake(string $make): void
{
$this->make = $make;
}
public function setModel(string $model): void
{
$this->model = $model;
}
public function setColor(string $color): void
{
$this->color = $color;
}
public function dump(): void
{
var_dump($this->make, $this->model, $this->color);
}
}
$car = new Car();
$car->setColor('pink');
$car->setMake('Ford');
$car->setModel('F-150');
$car->dump();
<div dir="rtl">
کلاس های final را ترجیح دهید
</div>The final
should be used whenever possible:
- It prevents uncontrolled inheritance chain.
- It encourages composition.
- It encourages the Single Responsibility Pattern.
- It encourages developers to use your public methods instead of extending the class to get access on protected ones.
- It allows you to change your code without any break of applications that use your class.
The only condition is that your class should implement an interface and no other public methods are defined.
For more informations you can read the blog post on this topic written by Marco Pivetta (Ocramius).
<div dir="rtl">بد:
</div>declare(strict_types=1);
final class Car
{
private $color;
public function __construct($color)
{
$this->color = $color;
}
/**
* @return string The color of the vehicle
*/
public function getColor()
{
return $this->color;
}
}
<div dir="rtl">
خوب:
</div>declare(strict_types=1);
interface Vehicle
{
/**
* @return string The color of the vehicle
*/
public function getColor();
}
final class Car implements Vehicle
{
private $color;
public function __construct($color)
{
$this->color = $color;
}
public function getColor()
{
return $this->color;
}
}
<div dir="rtl">
SOLID
SOLID مخففی است که توسط مایکل پرز برای پنج اصل اول به نام رابرت مارتین معرفی شده است، که به معنی پنج اصل اساسی برنامه نویسی و طراحی شی گرا است.
</div>- S: Single Responsibility Principle (SRP)
- O: Open/Closed Principle (OCP)
- L: Liskov Substitution Principle (LSP)
- I: Interface Segregation Principle (ISP)
- D: Dependency Inversion Principle (DIP)
Single Responsibility Principle یا SRP
همانطور که در Clean Code بیان شد ، "هرگز نباید بیش از یک کار برای یک کلاس وجود داشته باشد". بسته بندی کلاس هایی که قابلیت های زیادی دارند ، وسوسه انگیز است ، مانند زمانی که فقط اجازه دارید یک چمدان را در پرواز با خود حمل کنید. مسئله این است که کلاس شما از نظر مفهومی منسجم نخواهد بود و دلایل زیادی برای تغییر در آن ایجاد می کند. به حداقل رساندن تعداد دفعات لازم برای تغییر کلاس مهم است. این مهم است زیرا اگر کارایی بیش از حد در یک کلاس وجود دارد و شما بخشی از آن را اصلاح می کنید ، درک اینکه چگونه این امر بر سایر ماژول های وابسته در کد شما تأثیر می گذارد دشوار است.
بد:
</div>declare(strict_types=1);
class UserSettings
{
private $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function changeSettings(array $settings): void
{
if ($this->verifyCredentials()) {
// ...
}
}
private function verifyCredentials(): bool
{
// ...
}
}
<div dir="rtl">
خوب:
</div>declare(strict_types=1);
class UserAuth
{
private $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function verifyCredentials(): bool
{
// ...
}
}
class UserSettings
{
private $user;
private $auth;
public function __construct(User $user)
{
$this->user = $user;
$this->auth = new UserAuth($user);
}
public function changeSettings(array $settings): void
{
if ($this->auth->verifyCredentials()) {
// ...
}
}
}
<div dir="rtl">
Open/Closed Principle یا OCP
همانطور که برتراند مایر اظهار داشت ، "نهادهای نرم افزاری (کلاسها ، ماژولها ، توابع و ...) باید برای پسوند باز باشند، اما برای اصلاح بسته هستند."
این به چه معناست؟ این اصل در اصل بیان می کند که شما باید به افرادی که کد شما را می خوانند اجازه دهید ویژگی های جدید را بدون تغییر کد موجود اضافه کنند.
بد:
</div>declare(strict_types=1);
abstract class Adapter
{
protected $name;
public function getName(): string
{
return $this->name;
}
}
class AjaxAdapter extends Adapter
{
public function __construct()
{
parent::__construct();
$this->name = 'ajaxAdapter';
}
}
class NodeAdapter extends Adapter
{
public function __construct()
{
parent::__construct();
$this->name = 'nodeAdapter';
}
}
class HttpRequester
{
private $adapter;
public function __construct(Adapter $adapter)
{
$this->adapter = $adapter;
}
public function fetch(string $url): Promise
{
$adapterName = $this->adapter->getName();
if ($adapterName === 'ajaxAdapter') {
return $this->makeAjaxCall($url);
} elseif ($adapterName === 'httpNodeAdapter') {
return $this->makeHttpCall($url);
}
}
private function makeAjaxCall(string $url): Promise
{
// request and return promise
}
private function makeHttpCall(string $url): Promise
{
// request and return promise
}
}
<div dir="rtl">
خوب:
</div>declare(strict_types=1);
interface Adapter
{
public function request(string $url): Promise;
}
class AjaxAdapter implements Adapter
{
public function request(string $url): Promise
{
// request and return promise
}
}
class NodeAdapter implements Adapter
{
public function request(string $url): Promise
{
// request and return promise
}
}
class HttpRequester
{
private $adapter;
public function __construct(Adapter $adapter)
{
$this->adapter = $adapter;
}
public function fetch(string $url): Promise
{
return $this->adapter->request($url);
}
}
<div dir="rtl">
Liskov Substitution Principle یا LSP
بد:
</div>declare(strict_types=1);
class Rectangle
{
protected $width = 0;
protected $height = 0;
public function setWidth(int $width): void
{
$this->width = $width;
}
public function setHeight(int $height): void
{
$this->height = $height;
}
public function getArea(): int
{
return $this->width * $this->height;
}
}
class Square extends Rectangle
{
public function setWidth(int $width): void
{
$this->width = $this->height = $width;
}
public function setHeight(int $height): void
{
$this->width = $this->height = $height;
}
}
function printArea(Rectangle $rectangle): void
{
$rectangle->setWidth(4);
$rectangle->setHeight(5);
// BAD: Will return 25 for Square. Should be 20.
echo sprintf('%s has area %d.', get_class($rectangle), $rectangle->getArea()) . PHP_EOL;
}
$rectangles = [new Rectangle(), new Square()];
foreach ($rectangles as $rectangle) {
printArea($rectangle);
}
<div dir="rtl">
خوب:
</div>interface Shape
{
public function getArea(): int;
}
class Rectangle implements Shape
{
private $width = 0;
private $height = 0;
public function __construct(int $width, int $height)
{
$this->width = $width;
$this->height = $height;
}
public function getArea(): int
{
return $this->width * $this->height;
}
}
class Square implements Shape
{
private $length = 0;
public function __construct(int $length)
{
$this->length = $length;
}
public function getArea(): int
{
return $this->length ** 2;
}
}
function printArea(Shape $shape): void
{
echo sprintf('%s has area %d.', get_class($shape), $shape->getArea()).PHP_EOL;
}
$shapes = [new Rectangle(4, 5), new Square(5)];
foreach ($shapes as $shape) {
printArea($shape);
}
<div dir="rtl">
Interface Segregation Principle یا ISP
بد:
</div>declare(strict_types=1);
interface Employee
{
public function work(): void;
public function eat(): void;
}
class HumanEmployee implements Employee
{
public function work(): void
{
// ....working
}
public function eat(): void
{
// ...... eating in lunch break
}
}
class RobotEmployee implements Employee
{
public function work(): void
{
//.... working much more
}
public function eat(): void
{
//.... robot can't eat, but it must implement this method
}
}
<div dir="rtl">
خوب:
هر کارگری یک کارمند نیست ، بلکه هر کارمندی یک کارگر است.
</div>declare(strict_types=1);
interface Workable
{
public function work(): void;
}
interface Feedable
{
public function eat(): void;
}
interface Employee extends Feedable, Workable
{
}
class HumanEmployee implements Employee
{
public function work(): void
{
// ....working
}
public function eat(): void
{
//.... eating in lunch break
}
}
// robot can only work
class RobotEmployee implements Workable
{
public function work(): void
{
// ....working
}
}
<div dir="rtl">
Dependency Inversion Principle یا DIP
این اصل دو چیز اساسی را بیان می کند:
- ماژول های سطح بالا نباید به ماژول های سطح پایین وابسته باشند. هر دو باید به Abstractionها بستگی داشته باشند.
- Abstractionها نباید به جزئیات بستگی داشته باشد. جزئیات باید به Abstractionها بستگی داشته باشد.
بد:
</div>declare(strict_types=1);
class Employee
{
public function work(): void
{
// ....working
}
}
class Robot extends Employee
{
public function work(): void
{
//.... working much more
}
}
class Manager
{
private $employee;
public function __construct(Employee $employee)
{
$this->employee = $employee;
}
public function manage(): void
{
$this->employee->work();
}
}
<div dir="rtl">
خوب:
</div>declare(strict_types=1);
interface Employee
{
public function work(): void;
}
class Human implements Employee
{
public function work(): void
{
// ....working
}
}
class Robot implements Employee
{
public function work(): void
{
//.... working much more
}
}
class Manager
{
private $employee;
public function __construct(Employee $employee)
{
$this->employee = $employee;
}
public function manage(): void
{
$this->employee->work();
}
}
<div dir="rtl">
اصل DRY
کلمه ی DRY مخفف Don’t repeat yourself است. سعی کنید اصل DRY را رعایت کنید.
تمام تلاش خود را انجام دهید تا از نوشتن کدهای تکراری جلوگیری کنید. کد تکراری بد است زیرا به این معنی است که در صورت نیاز به تغییر منطق در ساختار برنامه، بیش از یک مکان برای تغییر دادن چیزی وجود دارد که باید آن را انجام دهید.
تصور کنید اگر یک رستوران اداره می کنید و موجودی خود را پیگیری می کنید: تمام گوجه فرنگی ، پیاز ، سیر ، ادویه جات و ... را در لیست نوشته باشید. اگر چندین لیست دارید که این مورد را در آن نگه می دارید ، پس هنگام تهیه یک غذا با گوجه فرنگی همه ی لیست ها باید به روز شوند ولی اگر فقط یک لیست موجودی دارید ، فقط یک جا برای به روزرسانی وجود دارد که باید آنرا تغییر دهید!!
اگر غالباً کد تکراری دارید علت آن این است که دو یا چند چیز کمی متفاوت دارید که اشتراکات زیادی با هم دارند، اما تفاوت آن ها شما را مجبور می کند که دو یا چند عملکرد جداگانه برای آن ها داشته باشید که بیشتر کارهای مشابه را انجام می دهند. حذف کد تکراری به معنای ایجاد انتزاعی است که می تواند مجموعه ای از موارد مختلف را فقط با یک تابع / ماژول / کلاس مدیریت کند.
درست گرفتن انتزاع بسیار مهم است، به همین دلیل باید از اصول SOLID مندرج در بخش Class ها پیروی کنید. انتزاعات بد ممکن است از کد تکراری بدتر باشد، بنابراین مراقب باشید! با گفتن این، اگر می توانید انتزاع خوبی انجام دهید، آن را انجام دهید! کدتان را مجددا تکرار نکنید، در غیر این صورت هر زمان که بخواهید یک چیز را تغییر دهید باید بخش های مختلفی را ویرایش کنید.
بد:
</div>declare(strict_types=1);
function showDeveloperList(array $developers): void
{
foreach ($developers as $developer) {
$expectedSalary = $developer->calculateExpectedSalary();
$experience = $developer->getExperience();
$githubLink = $developer->getGithubLink();
$data = [$expectedSalary, $experience, $githubLink];
render($data);
}
}
function showManagerList(array $managers): void
{
foreach ($managers as $manager) {
$expectedSalary = $manager->calculateExpectedSalary();
$experience = $manager->getExperience();
$githubLink = $manager->getGithubLink();
$data = [$expectedSalary, $experience, $githubLink];
render($data);
}
}
<div dir="rtl">
خوب:
</div>declare(strict_types=1);
function showList(array $employees): void
{
foreach ($employees as $employee) {
$expectedSalary = $employee->calculateExpectedSalary();
$experience = $employee->getExperience();
$githubLink = $employee->getGithubLink();
$data = [$expectedSalary, $experience, $githubLink];
render($data);
}
}
<div dir="rtl">
عالی:
بهتر است از نسخه فشرده کد استفاده کنید.
</div>declare(strict_types=1);
function showList(array $employees): void
{
foreach ($employees as $employee) {
render([$employee->calculateExpectedSalary(), $employee->getExperience(), $employee->getGithubLink()]);
}
}
<div dir="rtl">
ترجمه ها
ترجمه های این داکیومنت به زبان های دیگر:
- :cn: چینی:
- :ru: روسی:
- :es: اسپانیایی:
- :brazil: پرتغالی:
- :thailand: تایلندی:
- :fr: فرانسوی:
- :vietnam: ویتنامی
- :kr: کره ای:
- :tr: ترکی:
- :fa: فارسی: