| کانال توسعه‌دهندگان |
1.75K subscribers
55 photos
1 video
30 links
⭕️ کانال توسعه‌دهندگان دولوپیکس

💠 دولوپیکس | جامعه توسعه‌دهندگان ایرانی

💎 @Developix
🚀 Developix.ir

📌 پشتیبانی و تبلیغات:
@DevelopixSupport
Download Telegram
اصل Single Responsibility (SRP) از اصول معروف SOLID می‌گوید:

«هر ماژول، کلاس یا تابع باید فقط یک دلیل برای تغییر داشته باشد.» ✅

یعنی چی؟ یعنی یک کلاس نباید هم منطق کسب‌وکار را انجام دهد، هم لاگ بنویسد، هم با دیتابیس صحبت کند، هم ایمیل بفرستد. این دقیقاً همان جایی است که کد شروع به بو دادن می‌کند (Code Smell) 😅

نمونه‌ی بد: کلاس همه‌کاره
class UserService:
def register(self, user_data):
# validate
if not user_data["email"]:
raise Exception("email required")

# save to db
db.save(user_data)

# send welcome email
email_client.send(user_data["email"], "welcome")

# log
logger.info("user registered")


اینجا UserService چند مسئولیت دارد:

• validation ✅
• ذخیره در دیتابیس 🗄️
• ارسال ایمیل 📧
• logging 📜

تست کردنش سخت می‌شود، تغییر هر بخش روی بقیه اثر می‌گذارد، و هر تغییری ریسک Bug را بالا می‌برد.

بازطراحی با SRP
class UserValidator:
def validate(self, user_data):
if not user_data["email"]:
raise Exception("email required")

class UserRepository:
def save(self, user_data):
db.save(user_data)

class EmailNotifier:
def send_welcome(self, email):
email_client.send(email, "welcome")

class UserService:
def __init__(self, validator, repo, notifier, logger):
self.validator = validator
self.repo = repo
self.notifier = notifier
self.logger = logger

def register(self, user_data):
self.validator.validate(user_data)
self.repo.save(user_data)
self.notifier.send_welcome(user_data["email"])
self.logger.info("user registered")


حالا:

• هر کلاس یک مسئولیت واضح دارد ✔️
• تست واحد برای هر بخش ساده‌تر است ✔️
• تغییر ایمیل یا دیتابیس، UserService را نمی‌شکند ✔️

چند نکته‌ی کاربردی 🔍

• اگر در توضیح یک کلاس مجبورید از «و» زیاد استفاده کنید (این کلاس X و Y و Z انجام می‌دهد)، احتمالاً SRP نقض شده.
• متدهایی که بیش از حد طولانی‌اند و کارهای ناهمگن انجام می‌دهند، نامزد جدی Refactor هستند.
• SRP کمک می‌کند معماری تمیزتر داشته باشید و Design Patternها (مثل Strategy، Repository) را راحت‌تر پیاده کنید.

برای مطالعه‌ی بیشتر می‌توان به Refactoring.Guru - SRP یا فصل‌های ابتدایی کتاب Clean Code سر زد.

اجرای مستمر SRP روی کدهای روزمره، در بلندمدت پروژه‌ را قابل‌تغییرتر، تست‌پذیرتر و کم‌استرس‌تر می‌کند. از همین الان روی یکی از فایل‌های شلوغ پروژه شروع شود و مسئولیت‌ها را جدا کنید ✨

🔖 #Programming #برنامه_نویسی #Clean_Code #SOLID #SRP #Refactoring #Architecture #Design_Principles #Python

👤 Developix

💎 Channel: @DevelopixChannel
🚀 ساخت یک REST API ساده با FastAPI (ولی حرفه‌ای!)

FastAPI این‌قدر سریع و تمیزه که برای ساخت REST APIهای کوچک تا سرویس‌های جدی تولید (Production) عالی جواب می‌ده. توی این پست، بر اساس داک رسمی FastAPI (https://fastapi.tiangolo.com)، یک API واقعی و قابل استفاده برای مدیریت "تسک‌ها" می‌سازیم؛ چیزی شبیه todo-service کوچیک، ولی تمیز و قابل توسعه. 😎

ایده اینه:

• یک API می‌سازیم که لیست تسک‌ها رو بده، تسک جدید بگیره و وضعیتش رو عوض کنه.
• از Pydantic models برای اعتبارسنجی ورودی استفاده می‌کنیم.
• خروجی JSON تمیز و قابل مصرف برای Front-end یا mobile app داریم.

اول با نصب شروع کن:
pip install "fastapi[all]" uvicorn


حالا یک فایل به اسم main.py بساز و این کد رو بذار داخلش:

from typing import List, Optional
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

app = FastAPI(title="Tasks API")

class Task(BaseModel):
id: int
title: str
done: bool = False
description: Optional[str] = None

# دیتابیس موقتی در حافظه
TASKS_DB: List[Task] = []

@app.get("/tasks", response_model=List[Task])
async def list_tasks():
return TASKS_DB

@app.post("/tasks", response_model=Task, status_code=201)
async def create_task(task: Task):
for t in TASKS_DB:
if t.id == task.id:
raise HTTPException(status_code=400, detail="Task id already exists")
TASKS_DB.append(task)
return task

@app.patch("/tasks/{task_id}", response_model=Task)
async def mark_done(task_id: int):
for t in TASKS_DB:
if t.id == task_id:
t.done = True
return t
raise HTTPException(status_code=404, detail="Task not found")


اجرا روی لوکال:
uvicorn main:app --reload


بعد از اجرا، دو چیز خیلی کاربردی داری:

1️⃣ خود API روی:
http://127.0.0.1:8000/tasks


2️⃣ داکیومنت خودکار با Swagger UI روی:
http://127.0.0.1:8000/docs


همون‌جا می‌تونی POST /tasks رو تست کنی، JSON بفرستی و خروجی رو ببینی. این همون چیزی‌یه که تو سرویس‌های واقعی برای Front-end، موبایل یا حتی microserviceهای دیگه فرستاده می‌شه.

نکته‌های ریز ولی مهم:

• استفاده از response_model باعث می‌شه خروجی همیشه شکل مشخصی داشته باشه و Pydantic اون رو validate کنه.
• با HTTPException خطاها رو استاندارد و قابل پیش‌بینی برمی‌گردونی.
• چون TASKS_DB فقط تو حافظه‌ست، برای محیط واقعی باید به یک دیتابیس واقعی مثل PostgreSQL وصلش کنی (مثلاً با SQLModel یا SQLAlchemy).

این اسکلت ساده، پایه خیلی از APIهای واقعی‌ه: فقط کافیه endpointها و مدل‌هات رو گسترش بدی. 👨‍💻👩‍💻

امتحانش کن، یک mini todo-service برای خودت بساز و روی پروژه‌های شخصی یا تمرینی‌ات سوارش کن. اگر مفید بود برای بقیه دولوپرها هم بفرست تا از FastAPI لذت ببرن. 😄

🔖 #Programming #برنامه_نویسی #FastAPI #Python #REST_API #Backend #Web

👤 Developix

💎 Channel: @DevelopixChannel
👍3❤1
📌 سوال برنامه‌نویسی (Intermediate/Hard)

خروجی اجرای کد زیر در زبان Python چیست؟

دقت کنید که یک متغیر mutable بین فراخوانی‌های تابع به‌اشتراک گذاشته شده و فقط برای بعضی ورودی‌ها تغییر می‌کند.

🔖 #Programming #برنامه_نویسی

👤 Developix

💎 Channel: @DevelopixChannel
👍1
📌 یک سوال برای توسعه‌دهندگان Python:

به خروجی این کد دقت کنید. رفتار mutable default argument در Python اینجا کمی غافلگیرکننده است.

به‌نظر شما اجرای این کد چه خروجی‌ای در کنسول چاپ می‌کند؟

def add_item(item, items=[]):
items.append(item)
return items

print(add_item(1))
print(add_item(2))
print(add_item(3, []))
print(add_item(4))


پاسخ را به‌صورت دقیق و مطابق با خروجی واقعی Python بنویسید.

🔖 #Programming #برنامه_نویسی

👤 Developix

💎 Channel: @DevelopixChannel
🧪 استفاده از assert برای گرفتن باگ‌ها، قبل از دیر شدن

خیلی وقت‌ها یک فرض توی کد داریم که "مطمئنیم" همیشه برقرار است؛ مثلا این‌که ورودی خالی نیست یا مقدار نال نمی‌آید. این‌جور جاها assert دوست خوب ماست 😄

با assert می‌شود این فرض‌ها را به شکل چکِ خودکار داخل کد ثبت کرد تا اگر جایی نقض شد، همان‌جا و همان لحظه سرِ اجرا بترکد، نه دو روز بعد توی لاگ‌های مشتری.

یک مثال ساده در Python:
def average(nums: list[float]) -> float:
assert len(nums) > 0, "nums must not be empty"
return sum(nums) / len(nums)

print(average([1, 2, 3])) # 2.0
print(average([])) # AssertionError


نکته مهم 👇
- در Python اگر با فلگ -O اجرا شود، assertها حذف می‌شوند. پس برای validation ورودی کاربر از assert استفاده نمی‌شود؛ فقط برای چک‌کردن منطق داخلی و فرضیات برنامه‌نویس مناسب است.

امروز یکی از فرض‌های مهم کدت را با یک assert ساده محکم‌تر کن و نتیجه‌اش را در باگ‌های آینده ببین 🤝

📚 مرجع: Python Docs - assert statement

🔖 #Programming #برنامه_نویسی #assert #debugging #python #clean_code #defensive_programming

👤 Developix

💎 Channel: @DevelopixChannel
👍3🔥1
خروجی اجرای این کد در Python چیست؟

به تفاوت بین alias شدن لیست و ساختن کپی با slicing دقت کنید.

a = [[1], [2], [3]]
b = a
c = a[:]

b[0].append(99)
a[1] = [42]
c[2].append(7)

print("a =", a)
print("b =", b)
print("c =", c)


🔖 #Programming #برنامه_نویسی

👤 Developix

💎 Channel: @DevelopixChannel
👍1
در کد بالا در نسخهٔ اشتباه، همهٔ lambda ها به یک متغیّر مشترک i (به‌دلیل Late Binding در Python) اشاره می‌کنند و در نتیجه در زمان اجرا همگی آخرین مقدار حلقه را برمی‌گردانند؛ در حالی‌که در نسخهٔ درست با بستن مقدار i داخل پارامتر پیش‌فرض i=i برای هر iteration یک closure مستقل ساخته می‌شود و رفتار تابع‌ها قابل پیش‌بینی و درست خواهد بود. در چنین شرایطی استفاده از این تکنیک یا حتی تعریف تابع جداگانه به‌جای تکیه بر متغیر حلقه، هم خوانایی و هم پایداری کد را در پروژه‌های متوسط و بزرگ به‌طور محسوسی بهبود می‌دهد.

🔖 #Programming #برنامه_نویسی

👤 Developix

💎 Channel: @DevelopixChannel
👍2
گاهی وسط دیباگ سریع، کلی print می‌ریزیم تو کد و بعداً یادمان می‌رود پاکشان کنیم 😅 بهتر است از اول سراغ لاگ ساخت‌یافته (Structured Logging) برویم.

با لاگ، هم سطح اهمیت داریم (info, warning, error)، هم می‌توانیم به‌راحتی فیلتر و جستجو کنیم و هم در محیط‌های Production به‌درد می‌خورد.

مثال ساده در Python 👇
import logging

logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(name)s - %(message)s",
)

logger = logging.getLogger("payment-service")

user_id = 42
amount = 199_000

logger.info("starting payment", extra={"user_id": user_id, "amount": amount})

try:
1 / 0
except ZeroDivisionError:
logger.exception("payment failed")


✅ مزیت‌ها:
• لاگ‌ها قابل فیلتر بر اساس سطح هستند و می‌شود فقط warning/error دید.
• در سرویس‌های Microservice و ابزارهایی مثل ELK یا Grafana Loki خیلی تمیزتر تحلیل می‌شوند.

برای پروژه بعدی، از همان روز اول یک Logger درست و حسابی تنظیم شود تا Debug و Monitoring آینده خیلی راحت‌تر پیش برود 🚀

مرجع: Python logging (Official Docs)

🔖 #Programming #برنامه_نویسی #logging #debugging #best_practices #python #structured_logging

👤 Developix

💎 Channel: @DevelopixChannel
👍2❤1
‏اصل Single Responsibility (SRP)؛ وقتی کلاس‌ همه‌کاره خستگی‌ات می‌کند 😅

یکی از مهم‌ترین اصول SOLID، اصل Single Responsibility است:

هر کلاس یا ماژول باید فقط یک دلیل برای تغییر داشته باشد.

یعنی چی؟ یعنی یک کلاس نباید هم منطق دامنه را نگه دارد، هم log بنویسد، هم با دیتابیس کار کند، هم ایمیل بفرستد. این کلاس‌ها هم سخت تست می‌شوند، هم کوچک‌ترین تغییر، نصف سیستم را می‌ترکاند 🔥


❌ مثال بد: کلاس خدا (God Class)
class OrderService:
def create_order(self, data):
# 1) اعتبارسنجی ورودی
if not data.get("user_id"):
raise Exception("user_id required")

# 2) محاسبه قیمت
total = 0
for item in data["items"]:
total += item["price"] * item["qty"]

# 3) ذخیره در دیتابیس
db.save({"user_id": data["user_id"], "total": total})

# 4) ارسال ایمیل
EmailClient().send(
to=data["email"],
subject="Order created",
body=f"Total = {total}"
)

# 5) لاگ کردن
Logger().info("Order created", extra={"total": total})


این کلاس حداقل ۵ مسئولیت دارد. هر تغییری در validation، ایمیل، لاگ یا ساختار دیتابیس، این کلاس را درگیر می‌کند. تست واحد هم کابوس می‌شود 🤯


✅ بازطراحی با SRP

ایده این است که هر ماژول روی یک چیز متمرکز شود:

class OrderValidator:
def validate(self, data):
if not data.get("user_id"):
raise Exception("user_id required")

class PriceCalculator:
def calculate(self, items):
total = 0
for item in items:
total += item["price"] * item["qty"]
return total

class OrderRepository:
def save(self, user_id, total):
db.save({"user_id": user_id, "total": total})

class OrderNotifier:
def send_order_created(self, email, total):
EmailClient().send(
to=email,
subject="Order created",
body=f"Total = {total}"
)

class OrderService:
def __init__(self, validator, calculator, repo, notifier, logger):
self.validator = validator
self.calculator = calculator
self.repo = repo
self.notifier = notifier
self.logger = logger

def create_order(self, data):
self.validator.validate(data)
total = self.calculator.calculate(data["items"])
self.repo.save(data["user_id"], total)
self.notifier.send_order_created(data["email"], total)
self.logger.info("Order created", extra={"total": total})


حالا هر کلاس یک نقش نسبتاً واضح دارد. تغییر منطق قیمت، ایمیل یا دیتابیس، فقط همان بخش را تحت تاثیر قرار می‌دهد. تست واحد هم راحت‌تر است، چون می‌شود هر جزء را جداگانه mock و تست کرد ✅


چطور در عمل از SRP استفاده شود؟

• وقتی متدی خیلی طولانی می‌شود، معمولاً یعنی چند مسئولیت دارد.
• وقتی کلاس‌ مدام به خاطر انواع تغییرها ویرایش می‌شود، یعنی باید تفکیک شود.
• نام کلاس اگر شبیه "Manager"، "Helper"، "Util" باشد، اغلب بوی نقض SRP می‌دهد 😄

مرجع خوب برای مطالعه عمیق‌تر:
refactoring.guru/design-principles/srp

قدم‌های کوچک در جهت SRP، در طول زمان کد را تمیزتر، قابل‌تست‌تر و قابل‌تغییرتر می‌کند. از ریفکتور کردن کلاس‌های همه‌کاره امروز شروع شود ✨

🔖 #Programming #برنامه_نویسی #SRP #SOLID #Clean_Code #Refactoring #Design_Principles #Python

👤 Developix

💎 Channel: @DevelopixChannel
👍1
ساخت یک REST API ساده با FastAPI 🚀

اگر دنبال راهی سریع و تمیز برای ساختن API هستی، FastAPI یکی از بهترین گزینه‌هاست. روی Python ۳ کار می‌کنه، تایپ‌هینت‌ها رو جدی می‌گیره و همزمان هم داکیومنت اتوماتیک می‌سازه، هم performance خیلی خوبی داره.

سناریو: یه سرویس خیلی ساده «مدیریت تسک‌ها» می‌خوایم، چیزی شبیه TODO، که بعداً راحت بشه روش frontend یا موبایل سوار کرد.

منبع اصلی این آموزش از داکیومنت رسمی FastAPI هست:
https://fastapi.tiangolo.com/tutorial/

✅ ایده کلی

FastAPI روی Starlette و Pydantic ساخته شده. یعنی:

• تعریف مدل داده با Pydantic (مثلاً مدل Task)
• تعریف endpointها با decoratorهای ساده مثل @app.get
• اجرا روی Uvicorn برای performance بالا

این مدل برای ساخت microservice، backend برای SPA، یا API موبایل خیلی practical و واقعی‌ست.

🔧 نصب و راه‌اندازی سریع

۱) ساخت venv (اختیاری ولی پیشنهاد می‌شه)

python -m venv venv
source venv/bin/activate # روی ویندوز: venv\Scripts\activate


۲) نصب FastAPI و Uvicorn:

pip install "fastapi[all]" uvicorn


👨‍💻 پیاده‌سازی یک TODO API کوچک

فایل main.py رو بساز و کد زیر رو داخلش قرار بده:

from typing import List, Optional

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

app = FastAPI(title="Todo API")


class Task(BaseModel):
id: int
title: str
done: bool = False
description: Optional[str] = None


fake_db: List[Task] = []


@app.get("/tasks", response_model=List[Task])
async def list_tasks():
return fake_db


@app.post("/tasks", response_model=Task)
async def create_task(task: Task):
for t in fake_db:
if t.id == task.id:
raise HTTPException(status_code=400, detail="Task id already exists")
fake_db.append(task)
return task


@app.get("/tasks/{task_id}", response_model=Task)
async def get_task(task_id: int):
for t in fake_db:
if t.id == task_id:
return t
raise HTTPException(status_code=404, detail="Task not found")


@app.patch("/tasks/{task_id}", response_model=Task)
async def toggle_done(task_id: int):
for t in fake_db:
if t.id == task_id:
t.done = not t.done
return t
raise HTTPException(status_code=404, detail="Task not found")


▶️ اجرای سرور

روی ترمینال:

uvicorn main:app --reload


حالا API روی آدرس زیر در حال اجراست:

http://127.0.0.1:8000


و مستندات خودکار (Swagger UI) اینجاست:

http://127.0.0.1:8000/docs


اونجا می‌شه مستقیم request فرستاد، body داد، response دید و راحت API رو تست کرد. برای تست سریع هم می‌شه از curl یا Postman استفاده کرد.

💡 نکات کاربردی

• بهتره از همون اول type-hintها رو جدی بگیری؛ FastAPI ازشون برای validation و schema استفاده می‌کنه.
• در پروژه واقعی، به‌جای fake_db از دیتابیس (مثل PostgreSQL با SQLAlchemy) استفاده می‌شه، اما همین in-memory مدل فکری رو خوب جا می‌اندازه.
• @app.patch برای update جزئی خیلی به‌دردبخوره؛ مخصوصاً وقتی روی front با formهای تیک‌دار کار می‌کنی.

یک بار این API ساده رو بساز، چند تا درخواست POST و GET بزن و ببین چقدر سریع می‌شه یک backend تمیز بالا آورد. اگر مفید بود، برای بقیه تیم هم بفرست تا برای پروتوتایپ‌های بعدی از FastAPI استفاده کنند. 🚀

🔖 #Programming #برنامه_نویسی #FastAPI #Python #REST_API #Backend #Web #Programming #Tutorial

👤 Developix

💎 Channel: @DevelopixChannel
👍4
راز کندی کدهای مالتی‌ترد: کابوسی به نام False Sharing
تا حالا برات پیش اومده یه کد مالتی‌ترد بنویسی، هیچ Lockای هم استفاده نکنی، تردها کاملاً مستقل از هم کار کنن، ولی آخرش ببینی از نسخه‌ی تک‌ترد هم کندتره؟
اگر جواب مثبته، احتمالاً با یکی از عجیب‌ترین و پنهان‌ترین مشکلات دنیای سخت‌افزار روبه‌رو شدی: False Sharing.
پشت صحنه‌ی CPU چه خبره؟
پردازنده‌های امروزی داده‌ها رو دونه‌دونه از RAM نمی‌خونن. اون‌ها داده‌ها رو در بسته‌هایی به اسم Cache Line (معمولاً ۶۴ بایت) داخل حافظه‌ی کش (L1/L2/L3) نگه می‌دارن تا دسترسی بهشون خیلی سریع‌تر باشه.
حالا فرض کن این ساختار رو داریم:
type CoreData struct {
CounterA int64 // ۸ بایت
CounterB int64 // ۸ بایت
}
چون این دو متغیر کنار هم قرار گرفتن، احتمالاً هر دو داخل یک Cache Line قرار می‌گیرن.
مشکل از کجا شروع می‌شه؟
فرض کن:
ترد اول روی هسته‌ی اول CPU مدام CounterA رو تغییر می‌ده.
ترد دوم روی هسته‌ی دوم CPU هم مدام CounterB رو آپدیت می‌کنه.
از دید برنامه‌نویس، این دو متغیر هیچ ارتباطی با هم ندارن. اما از دید CPU داستان فرق می‌کنه.
هر بار که هسته‌ی اول CounterA رو تغییر می‌ده، کل اون Cache Line برای هسته‌ی دوم نامعتبر (Invalid) می‌شه. هسته‌ی دوم مجبور می‌شه دوباره داده رو از کش سطح بالاتر یا حتی RAM بخونه، مقدار خودش رو تغییر بده و دوباره باعث نامعتبر شدن کش هسته‌ی اول بشه.
این رفت‌وبرگشت دائمی بین هسته‌ها رو Cache Line Bouncing می‌گن.
نتیجه چیه؟ پردازنده به جای اینکه وقتش رو صرف اجرای کد تو کنه، مدام مشغول هماهنگ کردن حافظه‌ی کش بین هسته‌هاست!
راه‌حل؟ Padding
خوشبختانه راه‌حلش خیلی پیچیده نیست.
کافیه کاری کنیم این دو متغیر داخل یک Cache Line قرار نگیرن. یعنی بینشون کمی فاصله (Padding) ایجاد کنیم تا هر کدوم روی یک Cache Line جدا باشن.
مثلاً در Go می‌تونیم از cpu.CacheLinePad استفاده کنیم:
import "golang.org/x/sys/cpu"

type OptimizedData struct {
CounterA int64
_ cpu.CacheLinePad
CounterB int64
}
با همین فاصله‌ی ساده، هر متغیر روی یک Cache Line جدا قرار می‌گیره و دو هسته بدون اینکه مزاحم هم بشن، هم‌زمان کار می‌کنن.
جالب‌تر اینکه در بعضی بنچمارک‌های واقعی، همین تغییر کوچک تونسته عملکرد کدهای موازی رو چند برابر و حتی تا ۱۰ برابر بهتر کنه.
حرف آخر
خیلی وقت‌ها مشکل کند بودن کد، نه از الگوریتمه، نه از زبان برنامه‌نویسی و نه حتی از تعداد Threadها؛ بلکه از جزئیاتی میاد که داخل خود CPU اتفاق می‌افته.
هرچقدر درک بهتری از معماری سخت‌افزار داشته باشیم، راحت‌تر می‌تونیم کدهایی بنویسیم که علاوه بر درست بودن، واقعاً سریع هم باشن.

🔖 #Programming #برنامه_نویسی

👤 Ruhollah

💎 Channel: @DevelopixChannel
👍4❤1
‏قانون Demeter (Law of Demeter) چیه و چرا اینقدر مهمه؟ 🤐

یکی از اصول قدیمی ولی فوق‌العاده کاربردی در طراحی شی‌گرا، Law of Demeter یا همون "Talk only to your friends" ـه.

به زبان ساده: هر آبجکت فقط باید با "همسایه‌های نزدیکش" حرف بزنه، نه با نصف سیستم.

قاعدهٔ اصلی:
هر متد فقط باید با این‌ها تعامل داشته باشه:

• خودِ شی (this)

• پارامترهای ورودی متد

• فیلدهای خودش

• آبجکت‌هایی که خودش می‌سازه

هر چیزی غیر از این، بوی Coupling بالا و دردسر در ریفکتور می‌ده 😄

یک مثال بد 👎 (Law of Demeter نقض شده)
order.customer.address.city


توی کد واقعی (مثال Python):
class OrderService:
def get_customer_city(self, order):
return order.customer.address.city


اینجا OrderService داره نصف مدل دامنه رو می‌گرده:

• اگر ساختار Customer عوض بشه، اینجا هم می‌ترکه.

• اگر Address حذف بشه یا تغییر کنه، باز این کلاس می‌شکنه.

نسخهٔ بهتر ✅ (رعایت Law of Demeter)

مسیر طولانی رو "کپسوله" می‌کنیم:
class Customer:
def get_city(self):
return self.address.city

class Order:
def get_customer_city(self):
return self.customer.get_city()

class OrderService:
def get_customer_city(self, order):
return order.get_customer_city()


حالا OrderService فقط با Order حرف می‌زنه. بقیه‌اش پشت متدها مخفیه. اگر فردا مدل آدرس عوض بشه، فقط داخل Customer و حداکثر Order رو آپدیت می‌کنی.

چرا این قانون در عمل خیلی کمک می‌کنه؟ 💡

• کاهش Coupling: کلاس‌ها کمتر از ساختار داخلی هم خبر دارن.

• انعطاف در ریفکتور: راحت می‌تونی مدل داده رو عوض کنی بدون اینکه نصف سرویس‌ها رو دست بزنی.

• بهبود تست‌پذیری: با Mock کردن چند آبجکت محدود می‌تونی یونیت‌تست بنویسی، بدون زنجیره‌های عجیب.

بوی بد معروف: "Train Wreck" 🚂

هر وقت این الگو رو دیدی:
a.b().c.d().e


تقریباً مطمئنی که Law of Demeter نقض شده. این همون Train Wreck ـه؛ قطار نقطه‌ها.

چند نکتهٔ عملی برای کدنویسی روزمره

• اگر . زیاد پشت سر هم می‌بینی، معمولاً یعنی لازمه چندتا متد کوچک "واسط" اضافه کنی.

• به جای expose کردن کل آبجکت‌های داخلی، متدهای سطح بالاتر ارائه کن (مثل getCustomerCity()).

• توی Serviceها از Facade-های کوچک استفاده کن تا از جزئیات Domain دور نگه‌شون داری.

منابع بیشتر 📚

• Martin Fowler — Law of Demeter
• Refactoring Guru — Message Chains

از همین امروز، هر جا "قطار نقطه‌ها" دیدی، یک مکث کوتاه و یک ریفکتور کوچک می‌تونه معماری کدت رو حسابی تمیزتر کنه 🌱

🔖 #Programming #برنامه_نویسی #Law_of_Demeter #Clean_Code #Coupling #Design_Principles #OOP #Refactoring #Architecture

👤 Developix

💎 Channel: @DevelopixChannel
👍6
نام‌گذاری تمیز؛ اولین خط دفاع در Clean Code 🧹

یکی از عملی‌ترین ایده‌های کتاب Clean Code این است که «اسم خوب، نصف راه خوانایی کد است». خیلی از بدهی‌های فنی فقط با نام‌گذاری درست اصلاً به‌وجود نمی‌آیند.

👀 نشانه‌های نام‌گذاری بد

وقتی متغیر و تابع این‌طوری هستند:

def p(u):
if len(u) > 0:
for x in u:
if x[3] == 1:
send(x)


از روی اسم‌ها تقریباً هیچ چیز قابل‌فهم نیست. برای فهمیدن کد، مجبور می‌شویم پیاده‌سازی داخلی را خط‌به‌خط بخوانیم.

✅ چطور بهترش کنیم؟

۱️⃣ اسم باید «بگوید» چه‌کار می‌کند

def send_active_users(users):
if len(users) == 0:
return

for user in users:
if user.is_active():
send(user)


نام تابع، ورودی و شرط‌ها واضح شده‌اند. نیاز کمتری به کامنت داریم، چون خود کد توضیح‌دهنده است.

۲️⃣ از اسم‌های عمومی مثل data, info, tmp دوری کن

# بد
result = process(data)

# بهتر
normalized_orders = normalize_orders(raw_orders)


هرجا اسم مبهم می‌شود، خواننده باید در ذهنش حدس بزند؛ همین حدس‌ها منبع اصلی سوتفاهم در کد هستند.

۳️⃣ اسم تابع را فعل، اسم متغیر را اسم بگذار

# تابع با اسم اسمی (بدفهم)
user_status = user()


# واضح‌تر
status = get_user_status()


این الگوی ساده کمک می‌کند بدون نگاه به بدنه، از روی اسم‌ها بفهمیم هر چیزی «چیز است» یا «کار».

۴️⃣ از abbreviation بی‌معنی خودداری کن

# بد
cfg = read_cfg(pth)

# بهتر
config = read_config_file(path)


خودمان امروز معنای cfg را می‌فهمیم، ۶ ماه بعد یا برای همکار جدید، این تبدیل می‌شود به رمز.

۵️⃣ اسم‌ها را بر اساس domain انتخاب کن

استفاده از واژه‌های همان دامنه (Order, Invoice, Payment, Discount) کد را به زبان بیزینس نزدیک می‌کند و فهم سیستم را برای همه‌ی تیم آسان‌تر می‌کند.

📚 برای مطالعه بیشتر:
Clean Code, Chapter 2: Meaningful Names
Refactoring Guru - Code Smells

در هر refactor کوچک، فقط با بهتر کردن اسم‌ها می‌شود کیفیت طراحی را بالا برد؛ بدون تغییر رفتار کد. از همین commit بعدی، روی هر اسم چند ثانیه بیشتر فکر شود، بازدهش در بلندمدت شگفت‌انگیز است ✨

🔖 #Programming #برنامه_نویسی #clean_code #naming #meaningful_names #refactoring #readability #architecture

👤 Developix

💎 Channel: @DevelopixChannel
❤4
در نمونه‌کد بالا، استفاده از آرگومان پیش‌فرض قابل‌تغییر (مثل list) در تعریف تابع باعث بروز رفتار غیرقابل‌پیش‌بینی و Memory Leak منطقی در سناریوهای پیچیده می‌شود، چون همهٔ فراخوانی‌ها همان آبجکت پیش‌فرض را به‌اشتراک می‌گذارند. به‌جای آن می‌توانید با مقدار پیش‌فرض None و ساخت لیست جدید داخل تابع، هر بار یک state مستقل بسازید و از باگ‌های سخت‌رفع در کدهای چندماژوله و تست‌نویسی جلوگیری کنید.

🔖 #Programming #برنامه_نویسی

👤 Developix

💎 Channel: @DevelopixChannel
🧩 این کد پایتون چه باگ عجیبی داره؟

هر بار که تابع add_item رو صدا می‌زنی، یه نتیجه غیرمنتظره می‌گیری.
مشکل اصلی توی این پیاده‌سازی چیه و چطور درستش می‌کنی؟

🔖 #Programming #برنامه_نویسی

👤 Developix

💎 Channel: @DevelopixChannel
🔥 پروژه: سرور LLM روی مک Apple Silicon

روی مک با Apple Silicon کار می‌کنی و می‌خواهی LLM محلی سریع داشته باشی؟ پروژه omlx دقیقا برای همین است 🤓

یک inference server سبک که مدل‌ها را با continuous batching و کش روی SSD اجرا می‌کند و همه‌چیز از منوی بالای macOS مدیریت می‌شود؛ بدون ترمینال، بدون دنگ‌وفنگ.

برای تست و دیباگ اپ‌های هوش مصنوعی روی مک گزینه‌ٔ خیلی جمع‌وجوری است.
لینک گیت‌هاب را یک‌بار چک کن، شاید دقیقا همان چیزی باشد که دنبالش بودی.

⭐️ 1,102 stars this week

🔖 #Programming #برنامه_نویسی #هوش_مصنوعی #LLM #Apple_Silicon

💎 @DevelopixChannel
🔥3❤1
یادته اون باگ لعنتی که هر ریکوئست جدید همون timestamp قدیمی رو برمی‌گردوند؟ معمولاً مقصر همچین چیزیه 👇

اینجا تو تابع اول، از datetime.now() به‌عنوان آرگومان پیش‌فرض استفاده شده. این مقدار فقط یک‌بار موقع تعریف تابع حساب می‌شه، نه هر بار صدا زدن تابع؛ برای همین همهٔ لاگ‌ها همون زمان اولی رو می‌گیرن.

تو نسخهٔ درست، پیش‌فرض رو None گذاشتیم و داخل تابع اگر timestamp تهی بود، تازه اون‌جا datetime.now() رو صدا می‌زنیم. این‌طوری هر بار یه زمان جدید و درست ثبت می‌شه.

خلاصه‌اش: اگه می‌خوای یه مقدار «الان» یا یه لیست/دیکشنری خالی پیش‌فرض داشته باشی، از آرگومان پیش‌فرض قابل تغییر یا dynamic استفاده نکن؛ None بذار و داخل تابع مقدارش رو بساز 😉

🔖 #Programming #برنامه_نویسی

👤 Developix

💎 Channel: @DevelopixChannel
👍5
🔥 پروژه: چارچوب مهارت‌ توسعه‌دهنده «سوپِرپاورز»

سوپِرپاورز یک چارچوب مهارتی و متدولوژی توسعه نرم‌افزار است برای این‌که تبدیل به یک توسعه‌دهنده واقعاً «agentic» و صاحب‌اختیار در کار شوی.

داخل مخزن، راهنماها و چک‌لیست‌هایی برای مهارت‌هایی مثل اولویت‌بندی کار، دیباگ سیستماتیک، ارتباط با تیم و مدیریت ریسک پروژه وجود دارد؛ هم برای رشد فردی به‌درد می‌خورد، هم برای منتورینگ و لیدها 👀

از این‌جا می‌توان دیدش:
github.com/obra/superpowers

⭐️ 4,068 stars this week

🔖 #Programming #برنامه_نویسی #توسعه‌دهنده #متدولوژی #مدیریت_پروژه

💎 @DevelopixChannel
👍2
تو کدهای async اگه وسط کوروتین بری یه تابع بلاک‌کننده معمولی (مثلاً یه request سنگین) صدا بزنی، عملاً event loop رو می‌خوابی و کل مزیت async می‌پره.

توی مثال اول، تابع fetch_url مستقیماً از requests.get استفاده کرده و کل event loop رو بلاک می‌کنه؛ اگه چند تا fetch_url رو با هم gather کنی، عملاً به‌جای اجرای هم‌زمان، پشت سر هم اجرا می‌شن.

توی روش دوم، همون کار رو می‌بریم توی run_in_executor که I/O بلاک‌کننده رو بندازه روی thread جدا. این‌طوری event loop آزاد می‌مونه، کوروتین‌ها واقعاً concurrent اجرا می‌شن و تحت فشار تعداد زیاد request، برنامه خیلی روان‌تر عمل می‌کنه.

اگه مجبوری از کتابخونه‌های blocking مثل requests استفاده کنی، حتماً یه چیزی شبیه روش دوم پیاده کن یا کلاً مهاجرت کن به نسخه async اون کتابخونه (مثلاً به‌جای requests از httpx.AsyncClient یا aiohttp استفاده کن).

🔖 #Programming #برنامه_نویسی

👤 Developix

💎 Channel: @DevelopixChannel
خطای موقت شبکه، شکست دائمی نیست 🔁

با کتابخانهٔ Tenacity در پایتون می‌توانی برای عملیات ناموفق، retry تعریف کنی؛ بدون اینکه منطق تلاش مجدد را در هر تابع تکرار کنی.

نکته‌های کاربردی:
• تعیین سقف تلاش و فاصلهٔ بین تلاش‌ها
• پشتیبانی از توابع async
• محدود کردن retry به خطاهای مشخص

نصب:
pip install tenacity


در این نمونه فقط خطاهای شبکه دوباره امتحان می‌شوند؛ حداکثر ۳ تلاش با ۲ ثانیه فاصله:
from urllib.request import urlopen
from urllib.error import URLError
from tenacity import (
retry, stop_after_attempt,
wait_fixed, retry_if_exception_type,
)

@retry(
stop=stop_after_attempt(3),
wait=wait_fixed(2),
retry=retry_if_exception_type(URLError),
reraise=True,
)
def fetch(url):
with urlopen(url, timeout=5) as response:
return response.read()


برای عملیات پرداخت، retry بدون تضمین idempotency خطرناک است؛ از یک درخواست خواندنی شروع کن 🌱

مستندات رسمی

🔖 #Programming #برنامه_نویسی #Python #retry #Tenacity

👤 Developix

💎 Channel: @DevelopixChannel
👍2