برنامه نویسی ماژولار و غیر ماژولار
بریم سرِ گپِ دوستانه دربارهی «برنامهنویسیِ ماژولار» در برابرِ «غیرماژولار»؛ مثلِ اینه که داریم مقایسه میکنیم بین یه آشپزخونهی آپارتمانی شیک که هر کابینِ مشخصی یه کار میکنه، با یه انباری که همهی قابلمه و چاقو و برقوبُنالُف رو توی یه کارتنِ دربباز ریختی! هر دو غذا درست میکنن، ولی تفاوت توی «سرعتِ پیدا کردنِ» قاشقِ چایخوریه، توی «دردسرِ تمیز کردن»، توی «اضافه کردنِ» یه دستگاهِ جدید.
۱) تعریفِ ساده، مثلِ رسپی
ماژولار (Modular):
– میشیِنی یه «بخشِ مشخص» میسازی (مثلاً یه ماژولِ «محاسبهِ مالیات» یا «مدیریتِ LED»).
– اون بخش فقط یه درگاهِ کوچیکِ عمومیِ (interface) داره که بقیه ازش استفاده میکنن؛ داخلِ خودش مثلِ قلعهاس، هیچکس حق نداره مستقیم بره توی کشوهای درونیِ اون.
– اگر فردا خواستی قلعه رو عوض کنی، فقط همون درگاه رو حفظ میکنی؛ بقیهِ برنامه متوجه نمیشه.
غیرماژولار (Monolithic):
– همهچی توی یه فایلِ ۲۰۰۰ خطی یا یه کلاسِ «God-Object» ریخته.
– متغیری لازم داری؟ میری وسطِ کد نفرِ مقابل، خطِ ۱۵۳۴، عوضش میکنی!
– اگر بخوای یه قابلیتِ جدید بیاری، مجبوری چراغِ مطالعه بیاری، چون نمیدونی کدوم خط ممکنه بترکه.
۲) یه مثالِ ملموسِ «کدنویسی» برات مینویسم
فرض کن باید یه LED روشن کنیم، بعد دکمهای داریم که وقتی زدی، روشنموندنش چشمکزن بشه.
حالتِ غیرماژولار (همهچی تویِ main.c ریخته):
#include <avr/io.h>
int main(void){
DDRB &= ~(1 << PB0); // دکمهی ورودی
DDRB |= (1 << PB5); // LED خروجی
uint8_t mode = 0; // 0=ثابت روشن، 1=چشمک
while(1){
if(!(PINB & (1 << PB0))){ // دکمه زده شد؟
_delay_ms(50); // دbounce ساده
if(!(PINB & (1 << PB0)))
mode = !mode;
}
if(mode){
PORTB ^= (1 << PB5);
_delay_ms(200);
} else {
PORTB |= (1 << PB5);
}
}
}
کار میکنه؛ ولی حالا بخوام تعدادِ چشمکهارو محدود کنم، یا LED دوم اضافه بشه، یا دکمهِ دوم بیاد، باید بری داخلِ همین حلقهی عظیم، دستکاری کنی. اگر یه همتیمی بیاد، میگه «این mode کجا تعریف شده؟» باید بیفتی دنبالِ خطِ ۹!
حالتِ ماژولار (هرکسی توِ خونهی خودشه):
led.h
#pragma once
#include <stdint.h>
void led_init(void);
void led_on(void);
void led_toggle(void);
void led_blink(uint8_t times);
led.c
#include "led.h"
#include <avr/io.h>
#include <util/delay.h>
static uint8_t blink_count = 0;
static uint16_t delay_ms = 200;
void led_init(void){ DDRB |= (1 << PB5); }
void led_on(void){ PORTB |= (1 << PB5); blink_count=0; }
void led_toggle(void){ PORTB ^= (1 << PB5); }
void led_blink(uint8_t times){ blink_count = times*2; }
// تایمرِ ۱ms صدا بزن این ISR رو صدا بزن:
void led_tick(void){ // هر ۱ms صدا زده میشه
static uint16_t cnt=0;
if(blink_count){
if(++cnt >= delay_ms){
cnt=0;
led_toggle();
blink_count--;
}
}
}
button.h
#pragma once
#include <stdbool.h>
void button_init(void);
bool button_was_pressed(void); // rising-edge
button.c
#include "button.h"
#include <avr/io.h>
static bool last_state = true;
bool button_was_pressed(void){
bool current = (PINB & (1 << PB0)) != 0;
bool edge = (!last_state && current);
last_state = current;
return edge;
}
main.c
#include "led.h"
#include "button.h"
int main(void){
led_init();
button_init();
while(1){
if(button_was_pressed())
led_blink(5); // ۵ بار چشمک
led_tick(); // تیکِ ۱ms
_delay_ms(1);
}
}
حالا اگر بخوای LED دوم بیاری، فقط یه فایلِ led2.c میسازی؛ اگر بخوای دکمهِ دوم بیاری، button2.c رو میزنی. هیچکس نمیدونه داخلِ اون ماژول چه متغیرهایی هست؛ فقط «اینترفیس» رو میبینه.
۳) فرقِ کلیِ تفکیکشده
| ویژگی | غیرماژولار | ماژولار |
|---|---|---|
| اندازهِ فایل | چند هزار خط | هر فایل < ۳۰۰ خط |
| وابستگیِ متقابل (Coupling) | بالا؛ هرکی هرکی رو میشناسه | کم؛ فقط از طریقِ اینترفیس |
| تستپذیری | باید کل برنامه بیاد بالا | هر ماژول رو جداگانه یونیتتست میکنی |
| reuse | باید کُپیپیست کنی و دستکاری | همون led.c رو میبری پروژهِ بعدی |
| سرعتِ اولیهِ توسعه | سریع مینویسی، ولی اشکالگیری کُند | کندترِ شروع، ولی اشکالگیری سریع |
| همکاریِ تیمی | «کی خطِ ۱۵۳۴ رو عوض کرد؟!» | «فقط تویِ led.c تغییر دادم» |
۴) یه استعارهِ دیگه
غیرماژولار = یه قوطیِ شیرِ خشکشده؛ اگر بخوای فقط کاکائو بریزی، مجبوری کلِ درب رو بشکنی.
ماژولار = شیشهِ ادویهایِ دربدارِ جدا؛ فقط زعفرون رو برمیداری، بقیه جاشون باقی میمونن.
۵) چه جوری ماژولار بنویسم؟ (چکلیستِ کوچیک)
۱) Single Responsibility: هر فایل/کلاس فقط یه «علتِ تغییر» داشته باشه.
۲) Interface واضح: تابعهایِ عمومی رو توی .h یا public بذار؛ بقیه static یا private بماند.
۳) وابستگیِ یکطرفه: led.c میتونه تایمر صدا بزنه، ولی تایمر نباید بره داخلِ led.c دستکاری کنه.
۴) کپسولهسازی (Encapsulation): متغیرهایِ داخلی رو static کن کسی نبینه.
۵) ** Dependency Injection:** اگر button نیاز داره «زمانِ فعلی» رو بفهمه، بهش یه اشارهگرِ تابعِ get_millis() بده، نه اینکه مستقیم millis_counter رو بخونه.
۶) یه نمونهِ «ماژولارِ» پیشرفتهتر (C++)
led.hpp
#pragma once
#include <cstdint>
class Led{
public:
enum Mode{ ON, OFF, BLINK };
Led(volatile uint8_t& port, uint8_t pin);
void set(Mode m, uint16_t period_ms=0);
void tick(); // هر ۱ms صدا زده بشه
private:
volatile uint8_t& port_;
uint8_t pin_;
Mode mode_;
uint16_t counter_;
};
حالا اگر بخوای LEDِ RGB بسازی، فقط یه RgbLed میسازی که درونش سهتا Led داره؛ کسی بیرون نمیدونه پینها کجا وصلان.
۷) وقتی «ماژولار» بیشازحد بشه (Over-Engineering)
– فایلهایِ ۵۰ تا برای یه LED ساده!
– اینترفیسِ ۷ لایهای برای رسیدن از دکمه تا LED!
اصطلاحاً «ماژولارِ افراطی» میشه؛ تعادل رو رعایت کن؛ اگر پروژهت ۵۰۰ خطِ، همون دو تا ماژول کافیه.
۸) جمعبندیِ یکجملهای
کدنویسیِ ماژولار = میسازی «قطعههایِ لِگویی» که هرکدوم رو میتونی جداگانه بسازی، تست کنی، عوض کنی، بفروشی(!) بدون اینکه کلِ قلعه خراب بشه.
پست های مرتبط
27 آبان 1404
27 آبان 1404
27 آبان 1404
27 آبان 1404