چطور یک سورس کد تمیز و مرتب بنویسیم؟ راهنمای کامل برای برنامهنویسان
بیاید مقاله رو با یک صحنه آشنا شروع کنیم! 😄
فرض کنید چند هفتهست دارید روی یک پروژه کار میکنید. روز اول همهچیز خیلی مرتب بوده. یک فایل ساختید، چندتا تابع نوشتید و پروژه رو اجرا کردید. همهچیز عالیه!
روز دوم یک قابلیت جدید اضافه میکنید.
روز سوم یک فایل دیگه میسازید.
روز پنجم میگید: «این کد رو فعلاً اینجا میذارم، بعداً مرتبش میکنم.»
یک هفته بعد هم یک فایل به اسم test2.py درست میکنید.
بعد میشه:
project/
├── main.py
├── main2.py
├── test.py
├── test2.py
├── new.py
├── new2.py
├── final.py
├── final2.py
└── final-final.py
😂😂😂
اگر این ساختار براتون آشناست، بدونید تنها نیستید!
تقریباً همه برنامهنویسها حداقل یک بار چنین پروژهای داشتن. مشکل از اینجا شروع میشه که پروژههای ما معمولاً از چیزی که فکر میکنیم سریعتر بزرگ میشن.
اولش فقط یک برنامه کوچیکه. بعد یک قابلیت اضافه میکنیم، بعد دیتابیس، بعد API، بعد لاگین، بعد سیستم مدیریت کاربران و ناگهان میبینیم پروژهای که قرار بود یک برنامه ساده باشه، تبدیل شده به یک پروژه چند هزار خطی!
اینجاست که داشتن یک سورس تمیز و مرتب اهمیت پیدا میکنه.
توی این مقاله قراره قدمبهقدم ببینیم چطور میتونیم از همون روز اول یک سورس مرتب بسازیم؛ سورسی که هم خودمون راحت بفهمیمش، هم اگر یک برنامهنویس دیگه وارد پروژه شد، مجبور نباشه برای پیدا کردن یک فایل ساده کل پروژه رو زیر و رو کنه! 😅
اصلاً سورس تمیز یعنی چی؟
قبل از هرچیزی باید بدونیم منظورمون از «سورس تمیز» چیه.
سورس تمیز فقط به معنی کد تمیز نیست.
ممکنه داخل یک پروژه کدهای خیلی خوبی نوشته باشیم، اما ساختار پروژه افتضاح باشه. یا برعکس، ساختار فایلها عالی باشه ولی داخل هر فایل کدهای درهمریخته داشته باشیم.
یک سورس تمیز یعنی مجموعهای از چند چیز مختلف:
- ساختار منطقی پروژه
- نامگذاری درست فایلها و پوشهها
- جدا کردن مسئولیتهای مختلف
- کد قابل فهم و خوانا
- مدیریت درست تنظیمات
- حفظ اطلاعات حساس
- مدیریت Dependencyها
- مستندسازی مناسب
- تست و مدیریت خطا
- تاریخچه Git مرتب
یعنی وقتی کسی وارد پروژه میشه، باید بتونه با یک نگاه کلی بفهمه هر قسمت کجاست و چه کاری انجام میده.
اولین قدم: قبل از کدنویسی فکر کن!
یکی از اشتباههایی که خیلی از ما انجام میدیم اینه که سریع میریم سراغ کدنویسی.
IDE رو باز میکنیم، یک فایل میسازیم و شروع میکنیم به نوشتن کد.
بعد از چند ساعت تازه میفهمیم که اصلاً نمیدونیم پروژه قراره چه ساختاری داشته باشه! 😂
قبل از شروع پروژه، حتی اگر شده فقط ۱۰ دقیقه، کمی فکر کنید.
مثلاً از خودتون بپرسید:
- پروژه قراره چه کاری انجام بده؟
- دیتابیس داریم؟
- API داریم؟
- کاربر داریم؟
- چند بخش اصلی داریم؟
- قرار هست پروژه بزرگ بشه؟
لازم نیست از اول یک معماری فوقالعاده پیچیده طراحی کنید. فقط باید یک تصویر کلی از پروژه داشته باشید.
مثلاً اگر میخواید یک برنامه مدیریت فروشگاه بسازید، احتمالاً بخشهایی مثل کاربران، محصولات و سفارشها دارید.
پس از همان ابتدا میدونید که این قسمتها احتمالاً باید از هم جدا باشن.
همهچیز رو داخل main.py نریز! 😅
یکی از معروفترین اشتباهات پروژههای کوچک اینه که همهچیز داخل یک فایل قرار میگیره.
مثلاً:
project/
└── main.py
داخل main.py هم اینها رو داریم:
- اتصال به دیتابیس
- کدهای API
- منطق برنامه
- مدیریت کاربران
- تنظیمات
- ارسال ایمیل
- و کلی چیز دیگه!
اوایل شاید مشکلی ایجاد نکنه. ولی پروژه که بزرگ بشه، پیدا کردن یک قسمت خاص واقعاً سخت میشه.
بهتره مسئولیتها رو جدا کنیم.
مثلاً:
project/
├── app/
│ ├── api/
│ ├── database/
│ ├── models/
│ ├── services/
│ └── utils/
├── tests/
├── config/
├── main.py
├── requirements.txt
├── README.md
└── .gitignore
حالا اگر دنبال کد مربوط به دیتابیس باشیم، میدونیم باید داخل database دنبالش بگردیم.
اگر دنبال تستها باشیم، میریم سراغ tests.
اگر دنبال منطق اصلی برنامه باشیم، احتمالاً services جای مناسبیه.
این یعنی ساختار پروژه به جای اینکه جلوی ما رو بگیره، کمکمون میکنه.
اسم فایلها هم مهمه!
همونطور که اسم متغیر مهمه، اسم فایل هم مهمه.
مثلاً اینها خیلی واضح نیستن:
new.py
test2.py
helper2.py
data_final.py
final_version.py
خب helper2.py دقیقاً چه کمکی میکنه؟! 😂
بهتره اسم فایل بر اساس کاری که انجام میده انتخاب بشه.
user_service.py
database.py
email_service.py
authentication.py
product_repository.py
حالا اگر کسی فقط اسم فایلها رو ببینه، تقریباً میفهمه هرکدوم چه کاری انجام میدن.
هر فایل یک مسئولیت مشخص داشته باشه
این بخش یکی از مهمترین قسمتهای ساخت یک سورس تمیزه.
فرض کنید یک فایل داریم به اسم:
everything.py
داخلش هم دیتابیس داریم، هم API، هم لاگین، هم ارسال ایمیل!
این یعنی فایل ما مسئولیتهای زیادی داره.
بهتره اینها رو جدا کنیم:
database.py
api.py
authentication.py
email_service.py
حالا اگر سیستم ایمیل خراب شد، میدونیم باید کجا رو بررسی کنیم.
این مفهوم در پروژههای بزرگ خیلی مهمتر میشه. چون هرچه پروژه بزرگتر باشه، پیدا کردن کدها سختتر میشه.
تنظیمات پروژه رو از کد جدا کن
یکی از چیزهایی که خیلی وقتها داخل سورس قاطی میشه، تنظیمات پروژهست.
مثلاً:
DATABASE_HOST = "localhost"
DATABASE_PORT = 5432
DEBUG = True
برای یک پروژه کوچک شاید مشکلی نباشه، ولی وقتی تنظیمات زیاد بشه، بهتره مدیریت تنظیمات جدا باشه.
مثلاً:
project/
├── app/
├── config/
│ ├── settings.py
│ └── database.py
└── main.py
این کار باعث میشه تنظیمات پروژه یک جای مشخص داشته باشن.
اما یک نکته خیلی مهم وجود داره...
هیچوقت Secretها رو مستقیم داخل سورس نذار! 🚨
فرض کنید API Key شما اینه:
API_KEY = "123456789-secret-key"
حالا این پروژه رو روی GitHub یا GitLab Push میکنید.
تبریک! 😐
حالا ممکنه افراد دیگه هم به کلید شما دسترسی داشته باشن.
بهتره اطلاعات حساس رو از Environment Variable بگیریم:
import os
API_KEY = os.getenv("API_KEY")
در پروژههای محلی هم میتونیم از فایل .env استفاده کنیم:
API_KEY=your-secret-key
DATABASE_URL=your-database-url
و در .gitignore قرار بدیم:
.env
__pycache__/
.venv/
*.pyc
این کار باعث میشه فایلهای حساس یا غیرضروری وارد Repository نشن.
البته یادتون باشه اگر یک Secret قبلاً روی GitHub عمومی شده، فقط حذف کردنش کافی نیست؛ باید اون Secret رو باطل کنید و یک Secret جدید بسازید.
Dependencyها رو مدیریت کن
فرض کنید پروژه شما از چند کتابخانه استفاده میکنه.
مثلاً:
import requests
import flask
import sqlalchemy
حالا اگر پروژه رو برای یک دوست بفرستید، اون از کجا بفهمه چه کتابخانههایی باید نصب کنه؟
اینجاست که فایلهایی مثل requirements.txt وارد میشن.
مثلاً:
Flask==3.1.0
requests==2.32.3
SQLAlchemy==2.0.36
حالا نصب Dependencyها خیلی راحتتره.
pip install -r requirements.txt
در پروژههای مختلف ممکنه از روشهای دیگهای مثل pyproject.toml هم استفاده بشه؛ مهم اینه که روش نصب وابستگیهای پروژه برای بقیه مشخص باشه.
README رو دستکم نگیر!
خیلی از برنامهنویسها پروژه رو میسازن، روی GitHub میذارن و بعد README رو فراموش میکنن! 😅
در حالی که README میتونه اولین چیزی باشه که یک نفر درباره پروژه شما میبینه.
یک README خوب حداقل میتونه شامل این موارد باشه:
- اسم پروژه
- توضیح کوتاه
- ویژگیهای اصلی
- روش نصب
- روش اجرا
- نیازمندیها
- نحوه مشارکت در پروژه
- لایسنس
مثلاً:
# My Project
یک پروژه ساده برای مدیریت محصولات.
## نصب
```bash
pip install -r requirements.txt
```
## اجرا
```bash
python main.py
```
## ویژگیها
- مدیریت محصولات
- مدیریت کاربران
- اتصال به دیتابیس
لازم نیست README شما یک کتاب ۵۰۰ صفحهای باشه! فقط باید کسی که پروژه رو دریافت میکنه بتونه بفهمه چطور باید باهاش کار کنه.
کدنویسی تمیز؛ بخش مهمی از سورس تمیز
حالا که ساختار کلی پروژه رو مرتب کردیم، وقتشه به خود کدها هم برسیم.
کد تمیز یعنی وقتی یک فایل رو باز میکنیم، مجبور نباشیم برای فهمیدن هر خط، سه ساعت فکر کنیم! 😂
مثلاً این کد:
x = 15
y = 20
z = x * y
کار میکنه، ولی خیلی واضح نیست.
نسخه بهتر:
width = 15
height = 20
area = width * height
اینجا اسم متغیرها خودشون توضیح میدن چه اتفاقی افتاده.
پس لازم نیست همیشه کلی کامنت بنویسیم. گاهی یک اسم خوب میتونه بهتر از یک کامنت طولانی باشه.
کامنتها: یادداشتهای شخصی برای خودِ آیندهات
تا حالا شده کدی رو که ۶ ماه پیش نوشتی باز کنی و هیچی ازش سر در نیاری؟ برای من که هزار بار پیش اومده. دقیقاً اینجاست که کامنتها مثل یه قهرمان به دادت میرسن. کامنتها یادداشتهایی هستن که توی کد مینویسی، اما کامپیوتر نادیدهشون میگیره. اونا فقط برای خودت و همتیمیهات هستن.
اما یه کامنت خوب چی میگه؟ بیا با هم ببینیم:
- کامنت بد:
# این یک حلقه for است(خب معلومه دیگه!) - کامنت خوب:
# افزایش شمارنده تا سقف ۱۰ برای جلوگیری از حلقه بینهایت(دلیل کار رو توضیح میده)
یه قانون طلایی: کامنتت باید بگه "چرا"، نه "چی". "چی" رو کد میگه، "چرا" رو تو باید توضیح بدی. همچنین از کامنتهای بیمصرف مثل # TODO که هیچوقت انجامشون نمیدی هم دوری کن!
تابعها رو بیش از حد بزرگ نکن
فرض کنید یک تابع داریم که ۵۰۰ خطه و همهچیز رو انجام میده:
def process_everything():
# validate user
# connect database
# save data
# send email
# create log
# ...
pass
پیدا کردن مشکل در چنین تابعی واقعاً سخت میشه.
بهتره وظایف رو تقسیم کنیم:
def validate_user(user):
...
def save_user(user):
...
def send_welcome_email(user):
...
def create_user_log(user):
...
حالا هر قسمت مسئولیت مشخصی داره.
این همون جاییه که اصول Clean Code به تمیزتر شدن سورس کمک میکنن، ولی یادتون باشه Clean Code فقط یک بخش از ماجراست؛ ساختار کلی پروژه هم به همون اندازه مهمه.
کدهای تکراری رو مدیریت کن
اگر یک منطق مشخص رو در ده جای پروژه کپی کنیم، احتمال خطا زیاد میشه.
مثلاً:
total = price + price * 0.09
اگر این کد چندین بار تکرار شده باشه، تغییر نرخ مالیات سخت میشه.
میتونیم یک تابع بسازیم:
def calculate_price_with_tax(price, tax_rate):
return price + price * tax_rate
حالا هرجا نیاز داشتیم:
total = calculate_price_with_tax(price, 0.09)
ولی حواستون باشه! DRY به معنی این نیست که هر دو خط شبیه هم رو حتماً باید تبدیل به یک تابع کنیم. گاهی این کار باعث پیچیدهتر شدن پروژه میشه. همیشه باید ببینیم این دو بخش واقعاً یک مسئولیت دارن یا فقط اتفاقی شبیه هم هستن.
خطاها رو درست مدیریت کن
این کد رو ببینید:
try:
connect_to_database()
except:
pass
این یعنی هر اتفاقی افتاد، بیخیال! 😂
مشکل اینجاست که اگر برنامه خراب بشه، هیچ اطلاعاتی نداریم.
بهتره خطاهای مشخص رو مدیریت کنیم:
try:
connect_to_database()
except ConnectionError as error:
print(f"Database connection failed: {error}")
در پروژههای واقعی هم بهتره از Logging استفاده کنیم تا اتفاقات مهم پروژه قابل پیگیری باشن.
تستها رو فراموش نکن
یک سورس مرتب فقط سورسی نیست که ظاهر خوبی داشته باشه. باید مطمئن بشیم قسمتهای مختلفش درست کار میکنن.
مثلاً:
def add(a, b):
return a + b
assert add(2, 3) == 5
assert add(0, 5) == 5
assert add(-2, 2) == 0
در پروژههای بزرگتر میتونیم تستهای مختلفی داشته باشیم؛ مثل Unit Test و Integration Test.
شاید اول کار نوشتن تست کمی حوصلهسربر به نظر برسه، ولی وقتی پروژه بزرگ بشه، میفهمید چقدر ارزش داره.
Git؛ دفتر خاطرات پروژه! 😂
Git فقط برای این نیست که کدمون رو آنلاین ذخیره کنیم.
Git به ما کمک میکنه تاریخچه پروژه رو هم مدیریت کنیم.
ولی اگر Commitها این شکلی باشن:
update
fix
change
test
aaa
final
final2
final-real
final-real-2
خب... احتمالاً حتی خودمون هم نمیفهمیم چی به چی شده! 😂
بهتره Commitها واضح باشن:
feat: add user authentication
fix: handle invalid login
refactor: simplify database service
docs: update README
اینطوری چند ماه بعد هم میتونیم بفهمیم چه تغییراتی در پروژه انجام شده.
یک سورس شلوغ رو نجات بدیم!
حالا فرض کنید با یک پروژه قدیمی روبهرو شدیم:
project/
├── main.py
├── database.py
├── users.py
├── products.py
├── api.py
├── random.py
├── temp.py
├── helper.py
├── helper2.py
├── final.py
└── test2.py
اولین کاری که نباید بکنیم اینه که همهچیز رو یکدفعه پاک کنیم و از اول بنویسیم! 😅
بهتره مرحلهبهمرحله جلو بریم.
اول فایلهای بدون استفاده رو شناسایی میکنیم.
بعد فایلهای مرتبط رو دستهبندی میکنیم.
مثلاً:
project/
├── app/
│ ├── users/
│ ├── products/
│ ├── api/
│ ├── database/
│ └── services/
├── tests/
├── config/
├── main.py
├── requirements.txt
├── README.md
└── .gitignore
بعد کدهای داخل فایلها رو بررسی میکنیم.
کدهای تکراری رو پیدا میکنیم.
تابعهای خیلی بزرگ رو تقسیم میکنیم.
اسمهای نامفهوم رو تغییر میدیم.
و در نهایت تست میکنیم که چیزی خراب نشده باشه.
این کار رو بهش میگیم Refactoring.
قبل از انتشار سورس، این چکلیست رو ببین!
- ✅ آیا فایلهای اضافی حذف شدن؟
- ✅ آیا Secret یا API Key داخل پروژه وجود نداره؟
- ✅ آیا فایل
.envبه.gitignoreاضافه شده؟ - ✅ آیا
.gitignoreموارد ضروری رو پوشش میده؟ - ✅ آیا Dependencyهای پروژه مشخص هستن؟
- ✅ آیا README نوشته شده و کامل هست؟
- ✅ آیا اسم فایلها و پوشهها گویا و قابل فهم هستن؟
- ✅ آیا اسم متغیرها و توابع رسا هستن؟
- ✅ آیا کامنتهای مفید برای بخشهای پیچیده نوشته شده؟
- ✅ آیا کدهای تکراری زیادی وجود نداره؟
- ✅ آیا تابعهای خیلی بزرگ شکسته شدن؟
- ✅ آیا خطاهای مهم مدیریت شدن؟
- ✅ آیا پروژه تست شده و حداقل Unit Testهای اصلی نوشته شدن؟
- ✅ آیا Commitها قابل فهم و استاندارد هستن؟
- ✅ آیا پروژه روی یه محیط غیر از سیستم خودت هم تست شده؟
جمعبندی؛ سورس تمیز از روز اول ساخته میشه
نوشتن یک سورس تمیز قرار نیست یک کار عجیب و غریب باشه.
لازم نیست از همان روز اول پیچیدهترین معماری دنیا رو طراحی کنید یا برای یک پروژه ۲۰ خطی ۵۰ تا پوشه بسازید! 😄
اصل ماجرا اینه که از اول کمی فکر کنیم.
فایلها رو منطقی دستهبندی کنیم.
هر بخش مسئولیت خودش رو داشته باشه.
اطلاعات حساس رو از سورس جدا کنیم.
Dependencyها رو مشخص کنیم.
README بنویسیم.
از Git درست استفاده کنیم.
و در کنار همه اینها، کدمون رو هم تا جای ممکن خوانا و قابل فهم بنویسیم.
یادتون باشه یک سورس خوب فقط سورسی نیست که امروز اجرا بشه.
یک سورس خوب سورسیه که فردا هم بشه توسعهاش داد.
شاید امروز پروژه شما فقط ۱۰۰ خط کد داشته باشه. اما اگر از همون اول ساختار درستی داشته باشید، وقتی پروژه به ۱۰ هزار خط رسید، هنوز میتونید توش نفس بکشید! 😂
پس دفعه بعد که خواستید یک پروژه جدید شروع کنید، قبل از اینکه سریع برید سراغ نوشتن اولین خط کد، چند دقیقه صبر کنید و از خودتون بپرسید:
«اگر این پروژه بزرگ بشه، آیا ساختاری که امروز ساختم هنوز جواب میده؟»
اگر جواب این سؤال «آره» باشه، شما یک قدم بزرگ به سمت ساختن یک سورس حرفهای و قابل توسعه برداشتید. 🚀
در نهایت، برنامهنویس خوب فقط کسی نیست که بتونه کد بزنه؛ کسیه که بتونه یک پروژه رو طوری بسازه که خودش و بقیه برنامهنویسها بتونن به راحتی اون رو بفهمن، تغییر بدن و بزرگترش کنن.
و این دقیقاً همون چیزیه که یک سورس تمیز رو از یک سورس شلوغ و غیرقابل مدیریت جدا میکنه.
گفتگو درباره این پست
برای ثبت پیام ابتدا وارد حساب خود شوید.
هنوز پیامی ثبت نشده است.