برنامه نویسی

C ++ را به یک مکان بهتر شماره 1 تبدیل کنید: معنی بهتر چیست

بیایید کلمه “بهتر” را تعریف کنیم

در عنوان این سری ، من از این اصطلاح استفاده کردم “بهتر”، اما به عنوان یک مهندس ، من نمی توانم تحقیقات خود را با چنین هدف مبهمی شروع کنم. دنیای مهندسی نرم افزار آنقدر پیچیده و متنوع است که کلمه “بهتر” بسته به مورد استفاده می تواند معانی بسیار متفاوتی داشته باشد.

بیایید به عنوان مثال C ++ را بگیریم – C ++ در چه مواردی خوب است؟ خوب ، من فکر می کنم همه ما موافقیم که عملکرد خوبی دارد. یک صحبت عالی از جان کالب وجود دارد که در طی آن او می گوید عملکرد غیر سازگار چنان عمیق در هویت C ++ تعبیه شده است که هر وقت نیاز به توجیه داشتید که چرا C ++ ایمنی ، خوانایی یا چیز دیگری را به نفع عملکرد فدا می کند ، فقط می توانیم بگوییم “زیرا این C ++ است”. تمرکز روی عملکرد یک تصمیم طراحی دلخواه است.

از آنجا که من در جستجوی بهبود فرایند برای خودم هستم ، مجموعه ای از قوانین کلی را نیز تعریف می کنم که به نظر من خوب است و بعداً می توانم از آن برای قضاوت در مورد راه حل های دیگر استفاده کنم و شاید خودم را عملی کنم. من می خواستم این لیست کوتاه باشد ، بنابراین من با مجموعه ای از خواص زیر که یک فرآیند توسعه نرم افزار “بهتر” را برای من تعریف می کند ، به پایان رسید:

  • قوام و عدم ابهام
  • مقاومت در برابر ترجیحات فردی
  • سهولت توسعه و مختصر بودن

من تصور می کنم که ممکن است درک بلافاصله چه چیزی در هر یک از این نکات از نظر توسعه نرم افزار پنهان باشد ، بنابراین بیایید آنها را در چند مثال تجزیه و تحلیل کنیم.

قوام و عدم ابهام

هیچ چیز من را به اندازه ناسازگاری خیلی دیوانه نمی کند. من حتی گاهی اوقات راه حل های زشت تر را ترجیح می دهم ، فقط برای حفظ قوام با بقیه سیستم. این ممکن است به عنوان یک ترجیح فردی تلقی شود ، اما من در واقع سعی می کنم از آن به عنوان “حداقل در اکثر موارد” دفاع کنم. به این دلیل است که برای توسعه سریع و مؤثر نرم افزار ، ابزارها و سیستم های ما باید سازگار باشند. در غیر این صورت ، اگر نمی دانید چه چیزی را باید انتظار داشته باشید یا از هر تکرار توسعه غافلگیر شوید ، در یک سوراخ عمیق از عدم قطعیت ها قرار می گیرید که در آن هرگونه پیش بینی ، تخمین یا برنامه ها غیرممکن به نظر می رسد.

قوام و عدم ابهام در نحو زبان

بیایید با چند نمونه شروع کنیم. این کد چه کاری انجام می دهد؟

void Function()
{
    int* a = new int(12);
    std::cout << *a << std::endl;
    delete a;
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

خوب ، این یک نشانگر ایجاد می کند ، حافظه را اختصاص می دهد ، شماره 12 را چاپ می کند و حافظه را به نمایش می گذارد. و این کد چه کاری انجام می دهد؟

void Function()
{
    std::shared_ptr<int> a = std::make_shared<int>(12);
    std::cout << *a << std::endl;
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

از نظر عملکردی ، دقیقاً همان کار را انجام می دهد. بیایید نگاهی عمیق تر به آنچه که هر یک از این قطعه های کد را توصیف می کند ، بیندازیم.

مورد اول کمتر ایمن است زیرا کاربر را مجبور به مدیریت دستی حافظه می کند ، اما عملکرد سربار مربوط به پیشخوان مرجع اتمی را در آن معرفی نمی کند std::shared_ptr، بنابراین با تمرکز C ++ بر عملکرد غیر سازگار ، به خوبی طنین انداز است. استفاده از این کد همچنین در بسیاری از پروژه های تجاری C ++ مدرن ممنوع است.

مورد دوم ایمن تر است زیرا حافظه را به طور خودکار معامله می کند ، اما برخی از عملکردهای سربار را معرفی می کند ، که ممکن است به خصوص در برنامه های چند رشته ای قابل مشاهده باشد. این کد همچنین در بسیاری از پروژه های تجاری C ++ مدرن توصیه می شود.

می بینی با این کجا می روم؟ C ++ به نقطه‌ای رسید که در آن ما نه تنها 2 روش کاملاً متفاوت برای انجام همان کارها داریم ، بلکه در واقع ما را ممنوع اعلام کردیم که فرض اصلی این زبان را برآورده می کند. برای اینکه چیزها حتی خنده دار تر شود ، به یاد داشته باشید که نشانگرهای هوشمند ، که روش پیشنهادی هستند ، ارائه می دهند get عملکردی که به شما امکان می دهد به هر حال به نشانگر خام دسترسی پیدا کنید.

برای ساختن نحو یک زبان برنامه نویسی واضح ، به نظر می رسد که باید یک قانون مهم وجود داشته باشد:

باید یک و تنها یک راه استفاده از هر ویژگی زبانی وجود داشته باشد.

قوام و عدم ابهام در ایمنی و رسیدگی به خطا

من روش های زیادی برای رسیدگی به خطاها در C ++ دیده ام ، که هر یک از آنها توسط مهندسان سخت و سخت دفاع می کنند. بیایید نگاهی به 2 محبوب ترین آنها بیندازیم:

ErrorCode code = DoSomething();

if (code != ErrorCode::kSuccess) {
    std::cerr << "Error when doing something: " << code << std::endl;
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

try {
    const int value = DoSomething();
}
catch (const std::exception &e) {
    std::cerr << "Error when doing something: " << e.what() << std::endl;
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

البته ، هر دو آنها جوانب مثبت و منفی خود را دارند. در مثال اول ، شخص دوست داشتنی استثناء می گوید نه تنها چنین رویکردی مانع از بازگشت یک مقدار قابل استفاده می شود (زیرا باید یک کد خطا را برگرداند) ، اما حتی بدتر از آن ، کد خطای برگشتی ممکن است بدون بررسی یا بررسی نادرست باقی بماند و جریان کد با وجود خطا ادامه می یابد. علاوه بر این ، مسیر موفقیت مسیری است که در بیشتر موارد اجرا خواهد شد ، اما شما هنوز هم باید هر بار کد خطا را صریحاً بررسی کنید. از طرف دیگر ، با استثنائات فقط در صورت بروز خطایی هزینه را پرداخت می کنید. از نظر چنین مهندس ، مثال دوم باید بهتر باشد زیرا استثناء جریان کد را بلافاصله در هنگام بروز خطا خاتمه می دهد ، بنابراین نیازی به بررسی دستی نیست.

با این حال ، عاشق کد خطا ، این وضعیت را کمی متفاوت می بیند. استثناء گرفتن کافی نیست زیرا آنچه واقعاً مهم است ، اطمینان از وضعیت معتبر سیستم پس از پرتاب استثناء (ایمنی استثناء قوی) است. آیا عملکرد شما دانش کافی برای رسیدگی به استثنا دارد؟ اگر نه و شما باید استثناء را “بالا” انجام دهید ، آیا تماس گیرنده به درستی آن را اداره می کند؟ آیا هیچ یک از تماس گیرندگان از این استثناء می گیرند یا اینکه تمام برنامه را خاتمه می دهد؟ در صورت کدهای خطا ، چنین مشکلی وجود ندارد و رسیدگی به هر خطا قابل پیش بینی و خواندن آن بسیار آسان است.

کدام یک بهتر است؟ من هنوز جواب عینی و نهایی را پیدا نکردم ، اما آنچه می دانم این است که اگر رسیدگی به خطا ذاتاً در فرآیند توسعه نرم افزار تعبیه شود ، به نظر می رسد فرض خوبی است که:

باید یک مکانیسم رسیدگی به خطا وجود داشته باشد که در نحو زبان تعبیه شده باشد.

مقاومت در برابر ترجیحات فردی

بسیاری از مهندسین ترجیحات فردی قوی خود را در طی سالهای توسعه نرم افزار ایجاد می کنند. این عادت ها ممکن است به حدی قوی باشد که مواردی مانند کجا برای قرار دادن براکت های مجعد ، نحوه تماس با اعضای کلاس یا قرار دادن & در لیست استدلال عملکرد به رتبه دین افزایش می یابد و ممکن است در جلسات که در غیر این صورت برای حل مشکلات واقعی که در ابتدا این نرم افزار برای حل آن قرار داشت ، زمان زیادی مصرف کند. در زیر چند نمونه در مورد چگونگی تجلی این عادت ها ارائه می دهم.

مقاومت در برابر ترجیحات فردی در ساختار پروژه

بیایید به این 2 نمونه از 2 ساختار مختلف پروژه سطح بالا نگاه کنیم:

project_1
|- include
|  |- my_library
|     |- my_library.h
|- src
|  |- my_library
|     |- my_library.cpp
|  |- main.cpp
|- CMakeLists.txt
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

project_2
|- my_library.hpp
|- my_library.cpp
|- main.cpp
|- CMakeLists.txt
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

به مثال اول نگاه کنید و به این سؤال پاسخ دهید: آیا با چنین ساختار پروژه موافقید؟ من مطمئن هستم که بسته به ترجیحات شخصی و مواردی که شما فقط به آن عادت کرده اید ، ممکن است پاسخ متفاوت باشد:

  • شاید شما ترجیح می دهید از پرونده شتر برای نام پرونده استفاده کنید؟
  • شاید شما ترجیح می دهید قرار دهید my_library در پوشه اختصاصی خود و ایجاد include وت src پوشه های داخل آن؟
  • شاید شما ترجیح می دهید پرونده های هدر را با پرونده های .cpp در یک مورد نگه دارید my_library پوشه؟

هیچ یک از این سؤالات پاسخ دقیق و عینی ندارند ، بنابراین بیایید یک سؤال متفاوت بپرسیم: آیا شما موافقید که چنین ساختاری است:

  • پاک کردن
  • مقیاس پذیر
  • کاربردی
  • قابل حفظ

اگر جواب مثبت است ، پس چرا ما حتی اگر آنها هیچ تاثیری در توسعه پروژه نداشته باشند ، وقت خود را برای جستجوی پاسخ ها برای 3 سؤال قبلی در نظر می گیریم؟ ممکن است بگویید که به این دلیل است که می توانید یک ساختار پروژه متفاوت را ارائه دهید که واضح ، مقیاس پذیر و کاربردی نیز باشد. اگر این درست است و تمام این شرایط برآورده می شود ، می توانم کورکورانه با شما موافقم که به جای آنچه که در بالا پیشنهاد شده است ، ساختار خود را دنبال کنید. نکته در اینجا این است که ساختار پروژه باید از منظر برآورده کردن نیازهای خاص پروژه ارزیابی شود ، نه از منظر ترجیحات فردی و ذهنی.

حال بیایید از مثال دوم به ساختار پروژه نگاه کنیم که در آن شخصی تصمیم گرفت تمام پرونده های منبع را در پوشه ریشه مخزن نگه دارد و به همان سؤال پاسخ دهد: آیا با چنین ساختار پروژه موافق هستید؟ اگر تجربه ای در توسعه نرم افزار حرفه ای دارید ، شرط می بندم که موافق نیستید. اگر یک کتابخانه جدید به یک پرونده هدر با همان نام برخی از پرونده های دیگر نیاز داشته باشد ، چه کاری انجام می دهید؟ به طور معمول ، این مشکلی نخواهد بود ، اما شما نمی توانید 2 پرونده با یک نام در داخل یک پوشه داشته باشید ، بنابراین اکنون مشکلی دارید. آیا تصور می کنید چندین تیم در چنین مخزن کار می کنند؟ این ساختار نه تنها بد به نظر می رسد بلکه در واقع بد است زیرا از توسعه کارآمد نرم افزار جلوگیری می کند.

همه این بدان معنی است که مجموعه ای از ساختارهای پروژه وجود دارد که می تواند به صورت عینی به عنوان خوب و مجموعه ای از ساختارهای پروژه در نظر گرفته شود که می تواند به صورت عینی بد تلقی شود. منظور من از “عینی” منظور من این است که داوری از ترجیحات مهندسین انفرادی ناشی نمی شود ، بلکه از فرضیات و اهداف خاص پروژه است. این مجموعه ای از ویژگی ها است که باعث می شود ساختار پروژه خوب یا بد باشد و ظاهر آن مربوط به نام پرونده های پرونده شتر نیست. اگر یک ساختار پروژه یک بار انتخاب شود و اجرا شود ، این مسئله از بین می رود. علاوه بر این ، اگر یک ساختار دقیقاً تعریف شده باشد ، یک زنجیره ابزار ممکن است از این واقعیت برای بهبود تجربه کاربر استفاده کند. هرگز برای همه یک ساختار ایده آل وجود نخواهد داشت ، اما از آنجا که ما در دنیای ایده آل زندگی نمی کنیم ، من آن را با چنین جمله ای خلاصه می کنم:

هزینه نداشتن ساختار پروژه از پیش تعریف شده بیشتر از هزینه ضرورت برای تنظیم با ساختار ناقص تحمیل شده توسط فرآیند است.

مقاومت در برابر ترجیحات فردی در مدیریت وابستگی

تقریباً هر پروژه نرم افزاری (به جز این موارد بسیار کوچک) وابستگی به مؤلفه های نرم افزاری شخص ثالث دارد. اگر از توسعه دهندگان پایتون بپرسید دقیقاً چگونه باید یک کتابخانه را در یک پروژه نرم افزاری ادغام کنید ، آنها می گویند که این بسیار ساده است – شما فقط یک محیط مجازی ایجاد می کنید ، تماس بگیرید pip install برای هر آنچه شما نیاز دارید ، تماس بگیرید pip freeze > requirements.txt در هر کاری که دارید و انجام داده اید. از این به بعد شما requirements.txt پرونده ای که هر توسعه دهنده پایتون دیگر می داند با چه کاری باید انجام دهد.

با این حال ، در جهان C ++ ، چنین سؤالی ممکن است بحث برانگیز باشد و منجر به بحث طولانی شود. شاید از منبع ساخته شود؟ اما آیا باید پس از آن از مخزن اصلی کلون کنیم یا یک آینه ایجاد کنیم؟ چه کسی آن نسخه آینه را حفظ خواهد کرد؟ اگر یک کتابخانه منبع باز نباشد ، چه می شود؟ ممکن است پس از آن از یک مدیر بسته مانند Conan استفاده کنید؟ اما اگر کتابخانه خاصی در مخزن کانن موجود نباشد ، چه می شود؟ اگر کتابخانه در دسترس باشد ، اما در نسخه خاصی که توسط پروژه شما مورد نیاز است ، در دسترس نیست؟ شاید بهتر باشد آثار باینری هایی را که می توانید در مقابل آن بارگیری و پیوند دهید ، حفظ کنید؟

من همه این سؤالات را بارها در پروژه های مختلف شنیده ام و این باعث شد که فکر کنم این باید کار زنجیره ابزار باشد تا بتوانند وزنه برداری سنگین را در آن موضوع انجام دهند. زنجیره ابزار باید به توسعه دهنده اجازه دهد فقط در مورد چه وابستگی ، به چه شکلی (منبع یا باینری) و در چه نسخه هایی مورد نیاز باشد. من نتیجه گیری نهایی را به این صورت تدوین می کنم:

مکانیسم مدیریت وابستگی نباید مسئولیت توسعه دهنده برنامه باشد.

مقاومت در برابر ترجیحات فردی در کنوانسیون های مورد استفاده

به قطعه کد زیر نگاه کنید و سعی کنید به یک سؤال پاسخ دهید: کدام یک بهتر است؟

class SomeClass
{
public:
    SomeClass() : private_member_{0} {
        std::cout << "Constructed" << std::endl;
    }

private:
    int private_member_;
};
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

class SomeClass
{
  public:
    SomeClass()
    : m_privateMember{0}
    {
        std::cout << "Constructed" << std::endl;
    }

  private:
    int m_privateMember;
};
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

در هر دو مورد ، ما یک کد C ++ کاملاً معتبر داریم ، پس چرا برای بسیاری از افراد که یکی از آنها را انتخاب می کنند اغلب موضوع زندگی و مرگ است؟ رویکردهای مربوط به موضوع وجود دارد ، به عنوان مثال پایتون بیشتر از ترجیحات قالب بندی خلاص شده است – هیچ براکت وجود ندارد و تعداد زبانه ها بر جریان کد تأثیر می گذارد ، بنابراین قالب بندی به نوعی توسط خود زبان اجرا می شود. پایتون همچنین در مورد کنوانسیون نامگذاری متغیرهای عضو کلاس موفقیت هایی دارد – با موفقیت اجرا کرد که یک متغیر عضو خصوصی باید با آن شروع شود __ و یک متغیر عضو عمومی نباید ، اما در مورد متغیرهای عضو محافظت شده شکست خورد. فقط طبق کنوانسیون ، آنها باید با یک زیربنای واحد شروع کنند ، اما اجرا نمی شود. در C ++ ، جایی که هیچ کنوانسیون مشترکی وجود ندارد ، در مورد اجرای هرگونه قوانینی در آن منطقه ، آزادی بیش از حد منجر به اتلاف وقت و تأثیر واقعی بر نرم افزار نمی شود. اکنون ممکن است بپرسید: بنابراین کاملاً به کنوانسیون های مورد استفاده اهمیت نمی دهید؟ خوب ، البته من کنوانسیون های مورد علاقه خود را دارم ، اما هر چه بیشتر در یک محیط پیچیده از یک پروژه بزرگ نرم افزاری تجاری کار کنم ، بیشتر به کد اهمیت می دهم که سازگار و درک آن باشد. چیز واقعی که به آن کمک می کند این است:

مهم نیست که از کدام کنوانسیون استفاده می شود ، اما داشتن یک کنوانسیون واحد که به طور مداوم در کل پایه کد استفاده می شود بسیار مهم است.

سهولت توسعه و مختصر بودن

توسعه آسان تر به معنای زمان کمتری برای اجرای و زمان کمتری برای اجرای آن به معنای پول بیشتر است. به همین دلیل بدیهی به نظر می رسد که سهولت توسعه باید یکی از عوامل مهم هنگام طراحی ابزارهای جدید باشد ، اما تعریف تعجب آور دشوار است که در واقع توسعه آسان است. من یک بار شنیده ام که:

اگر وقت بیشتری را صرف اشکال زدایی دانش زبان برنامه نویسی خود می کنید ، زبان برنامه نویسی امکان توسعه آسان را فراهم نمی کند تا مشکل واقعی که می خواهید حل کنید.

بیایید نمونه ای بی اهمیت را به دست آوریم تا چیزی برای کار در چاپ “سلام جهان” داشته باشیم. در نگاه اول ، ممکن است به نظر برسد که زبانهای اسکریپت باید پادشاهان باشند. در پایتون ، این یک کار یک خط است:

print("Hello world")
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

در Bash این یک 2 خط است:

#!/bin/bash
echo "Hello world"
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

مقایسه C ++ با آن مانند یک شوخی غم انگیز به نظر می رسد. با این حال ، به دلایلی ما در همه جا از زبانهای اسکریپت استفاده نمی کنیم. ما می خواهیم کد به جای تفسیر گردآوری شود ، می خواهیم آثار باینری را به کتابخانه های جداگانه تقسیم کنیم ، می خواهیم تصمیم بگیریم که آیا این کتابخانه ها باید بصورت آماری یا پویا پیوند داشته باشند.

برخی از شما ممکن است اکنون بگویید که اساساً سیب ها را با گلابی مقایسه می کند و چگونه می توانم حتی در همان جمله یک زبان اسکریپت تفسیر شده مانند پایتون و زبان کامپایل شده مانند C ++ قرار دهم. خوب ، من می توانم چون از نظر واسط که زبان ارائه می دهد ، مهم نیست. از نظر فنی ، هیچ مانعی برای ساده تر و مختصر تر رابط C ++ وجود ندارد (NIM به نوعی موفق به انجام این کار شد) ، همین کار را برای ابزار انجام می دهد. این فقط موضوع تصمیمات طراحی و نسبت بین کارهایی است که توسط کاربر و کارهایی که توسط ابزار انجام می شود انجام می شود.

اما آیا پایتون واقعاً برای همه مردم آسان است؟ آیا C ++ برای همه آنها سخت است؟ متأسفانه ، هر دو پاسخ عبارتند از: نه. این باعث می شود فکر کنم که ما فقط باید این واقعیت را بپذیریم که “سهولت” توسعه همیشه ذهنی است و هر تصمیمی در آن منطقه باید دلخواه باشد. با این حال ، برای اینکه به خودم یک نکته لنگر بدهم ، آن را خلاصه می کنم:

این ابزار برای حل کردن نیاز به توجه بیشتر از مشکل واقعی ندارد.

تعریف مختصر نیز دشوار است ، اما به نظر می رسد که وقتی چیزی بیش از حد کلامی است ، حداقل توافق در مورد آن آسان تر است. بسیاری از توسعه دهندگان C ++ که من با آنها ملاقات کرده ام ، تمایل دارند یک دیدگاه مشترک را به اشتراک بگذارند که C ++ حتی برای چیزهای ساده نیز به تایپ بیش از حد نیاز دارد. به عنوان مثال ، در خطی که یک نشانگر مشترک ثابت را به یک شی ثابت و تخصیص حافظه برای آن نگاه کنید ، نگاه کنید:

const std::shared_ptr<const int> ptr = std::make_shared<const int>(12);
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

این 71 کاراکتر برای دستیابی به چنین چیز اساسی است و من حتی این موارد را نیز حساب نکردم #include که در این مورد نیز اجباری است. مثال دیگر می تواند تعریف یک شیء عملکرد ساده باشد که دو شناور را اضافه می کند:

std::function<float(float, float)> func = [](float a, float b) -> float { return a + b; };
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

این یکی 90 شخصیت طول دارد. بله ، من این را می دانم -> float بخشی را می توان حذف کرد ، اما در اینجا من نیز شخصیت ها را حساب نکردم #include دستورالعمل که 21 کاراکتر اضافی است. خیلی طولانی حتی اگر از نوع بازگشت لامبدا خلاص شوم.

بنابراین “بهتر” به چه معنی است؟

تعریف کلمه “بهتر” بسیار دشوارتر از آنچه انتظار داشتم معلوم شد ، اما تعریفی که من برای خودم ایجاد کردم می گوید ، فرآیند توسعه نرم افزار در هنگام داشتن خصوصیات زیر بهتر است:

  • سازگار و غیر مبهم
    • باید یک و تنها یک روش برای استفاده از هر ویژگی زبانی وجود داشته باشد
    • باید یک مکانیسم رسیدگی به خطا وجود داشته باشد که در نحو زبان تعبیه شده باشد
  • مقاومت در برابر ترجیحات فردی
    • هزینه نداشتن ساختار پروژه از پیش تعریف شده بیشتر از هزینه ضرورت برای تنظیم با ساختار ناقص تحمیل شده توسط فرآیند است
    • مکانیسم مدیریت وابستگی نباید مسئولیت توسعه دهنده برنامه باشد
    • مهم نیست که از کدام کنوانسیون استفاده می شود ، اما داشتن یک کنوانسیون واحد که به طور مداوم در کل پایه کد استفاده می شود بسیار مهم است
  • سهولت توسعه
    • این ابزار برای حل مشکل از مهندس بیش از مشکل واقعی لازم نیست

بعد از خواندن آن ، ممکن است این تصور را داشته باشید که من فقط از آزادی متنفر هستم که زبانهایی مانند C ++ یا پایتون می دهند. ممکن است حداقل در بعضی قسمت ها درست باشید. یک بار آلبرت انیشتین گفت “کارها را تا حد امکان ساده تر کنید ، اما ساده تر نیست” و اگر بخواهم این مقاله را فقط با یک جمله خلاصه کنم ، نقل قول آلبرت انیشتین را به:

فرایند توسعه نرم افزار باید به همان اندازه که لازم باشد آزادی دهد ، اما نه بیشتر.

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا