طبقه بندی Bad Code Smell ها
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: هفت دقیقه

نقل قول‌های زیادی، در مورد کیفیت کد وجود دارند. دستور العمل‌های فراوانی نیز در این راستا وجود دارند. یکی از ابزارهایی که برای نوشتن کدهایی با کیفیت مطلوب وجود دارد، مجموعه الگوهای بد کد نویسی است که به Code smell یا بوی بد کد مشهور هستند.  
بوی بد کد، نشانه‌هایی در کد هستند که حکایت از مشکلات عمیق‌تری دارند. بوی بد کد مساوی با باگ نیست. ولی خطر افزایش باگ‌ها و یا مشکلاتی را در آینده، به دنبال خواهند داشت. بوی بد کد معمولا حاصل رعایت نکردن یک سری اصول اولیه برنامه نویسی و یا طراحی شیء گرا هستند. 
برای بهبود کیفیت نرم افزار در دراز مدت نیاز است موارد بوی بد کد به دقت بررسی و رفع شوند. رفع شدن آنها ریسک انباشته شدن بوی بد کد را در پروژه کم خواهد کرد. یکی از فواید جلوگیری از انباشته شدن چنین الگوهای بدی در پروژه، بهبود فرآیند نگهداشت آن می‌باشد که موضوعی بسیار مهم برای چابکی یک تیم نرم افزاری است. 
هنگام مشاهده‌ی بوی بد، در بخشی از کدها، معمولا اولین اقدام، رفع آن است (Refactoring). در فرآیند رفع آن ممکن است الگوهای بد دیگری در کد یافت شوند که با آنها نیز به همین صورت برخورد خواهد شد. 
انوع بوهای بد کد به دسته‌های زیر طبقه بندی می‌شوند. 

کدهای متورم (Bloaters) 


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

1 - متدهای بلند (Long method): در این الگوی بد، متدها تعداد خطهای زیادی از کد را شامل می‌شوند. به طور معمول متدهایی با تعداد خطوط بیشتر از 10 خط، متدهای بلند محسوب می‌شوند. نکته قابل توجه این است که هیچ کس متدی را با تعداد خطوط زیاد طراحی نمی‌کند! معمولا به مرور زمان تعداد خط‌های یک متد افزایش می‌یابند. 
2 - کلاس‌های بزرگ (Large class): کلاسی که تعداد فیلدها، متدها و خطوط کد زیادی دارد. 
3 - وسواس استفاده از متغیرهای داده‌ای اولیه (Primitive obsession): این بوی بد معمولا به سه شکل بروز می‌کند. 
  • استفاده از متغیرهای اولیه بجای ساختارهای کوچک برای کارهای اولیه مانند Currency, DateTime, PhoneNumber 
  • استفاده از constant‌ها برای کد کردن اطلاعات مانند USER_ADMIN_ROLE = 1 
  • استفاده از constant‌های رشته‌ای به عنوان نام فیلدها در آرایه‌های داده 
4 - تعداد پارامترهای زیاد متد (Long parameter list): تعداد پارامترهای بیشتر از سه یا چهار عدد در یک متد. 
5 - توده داده (Data clumps): در بعضی موارد ممکن است از متغیرها به صورت دسته‌ای در مکان‌های مختلف کد استفاده شود. مانند استفاده از دسته‌ای از متغیرها برای نگه داشتن اطلاعات مربوط به اتصال پایگاه داده. این دسته‌ها باید به کلاس‌های حمل کننده داده خود تغییر کنند. 
 

بد استفاده کنندگان از شیء گرایی (Object orientation abusers)  


تکه کدهای این بخش در واقع بد استفاده کنندگاه یا ناقص استفاده کنندگان از اصول شیء گرایی هستند. در این دسته بندی موارد زیر وجود دارند:  

1 - گذاره‌های switch: وجود یک گذاره switch پیچیده یا دنباله‌ای از گذاره‌های if  
2 - درخواست رد شده (Refused request): در این حالت یک کلاس مجموعه محدودی از اعضای کلاس پدر خود را پیاده سازی می‌کند و باقی اعضای کلاس پدر یا بدون استفاده می‌مانند یا با استفاده از پرتاب کردن استثناء (Exception throwing) از کار انداخته می‌شوند. 
3 - فیلد موقتی (Temporary field): در این حالت متغیرها مقدار خود را در شرایط خاصی می‌گیرند و در بقیه شرایط خالی هستند. 
4 - کلاس هایی دقیقا مشابه در کارایی ولی متفاوت در مشخصات (Alternative Classes with Different Interfaces): دو کلاس دقیقا یک کار را انجام می‌دهند ولی نام اعضای آنها (متد و ...) متفاوت است. 

جلوگیری کنندگان از تغییر(Change preventers) 


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

1 - تغییر واگرا (Divergent change): این حالت زمانی اتفاق می‌افتد که برای اعمال یک تغییر به کلاس نیاز است متدهای زیادی را تغییر دهید. به طور مثال به ازای هر نوع محصولی که به محصولات شما اضافه می‌شود باید متدهای ذخیره، بازیابی، جستجو را تغییر دهید. 
2 - Shotgun Surgery: این حالت شباهت زیادی به تغییر واگرا دارد. تنها تفاوت آن این است که در این حالت شما به ازای هر تغییر نیاز است کلاس‌های زیادی را تغییر دهید. تغییر واگرا در بدنه یک کلاس اتفاق می‌افتد. 
3 - سلسله مراتب موازی ارث بری (Parallel inheritance hierarchy): این مورد یکی کمتر درک شده‌ترین موارد است. در این حالت زمانی که یک زیر کلاس برای یک کلاس ایجاد می‌کنید به ازای آن ناخودآگاه مجبور می‌شوید یک زیر کلاس برای کلاس دیگری ایجاد کنید. 

کدهای غیر ضروری (Dispensables) 


این دسته از کدها معمولا کدهایی هستند بی دلیل و بی استفاده. کدهایی که نبودنشان بهتر از بودنشان است! حذف کردن این کدها به خوانایی و قابلیت نگهداری کد خواهد افزود. بوهای بدی که در این دسته بندی قرار دارند به صورت زیر می‌باشند: 

1 - کامنت: یک متد، با مقادیر فراوانی از کامنت‌های توضیحی پر شده است. 
2 - کد تکراری: در این بوی بد، دو قطعه کد دقیقا مانند یکدیگر هستند. 
3 - کلاس داده (ِData class): کلاس‌هایی که تنها فیلدهای اطلاعاتی در آنها وجود دارند و متدهای خامی که جهت دریافت یا ذخیره اطلاعات در آنها استفاده می‌شوند. این کلاس‌های معمولا هیچ روال منطقی ای در خود ندارند. یکی از قدرت‌های شیء گرایی افزودن رفتار به کلاس‌ها در کنار اقلام اطلاعاتی موجود در آن است.  
4 - کلاس تنبل (Lazy class):  اگر کلاس کار چندانی که درخور نگهداری و توسعه باشد، انجام نمی‌دهد بهتر است از بین برود. 
5 - کد مرده (Dead code): متغیر، پارامتر، متد یا کلاسی که دیگر هیچ استفاده‌ای از آن متصور نیست و هیچ استفاده‌ای در حال حاضر از آن وجود ندارد. 
6 - کلی نگری بیش از اندازه (Speculative Generality): این الگو نیز کدهایی را شامل می‌شود که بلااستفاده هستند. ولی دلیل بلااستفاده بودن آن کلی نگری و دور اندیشی بدون دلیل است. معمولا کدهای تولیدی برای شرایط فعلی و پیش‌بینی آینده تولید می‌شوند. اگر این پیش‌بینی آینده به درستی و بر مبنای واقعیات انجام نشود، معمولا نتیجه کار، طراحی و پیاده سازی ای بی فایده و بلااستفاده خواهد بود. 

کدهایی بیش از اندازه وابسته به هم (Couplers) 


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

1- متد حسود (Feature envy): متدی که از اعضای یک شیء دیگر بیشتر از اعضای کلاس خود استفاده می‌کند! این اتفاق معمولا زمانی می‌افتد که فیلدهایی به یک "کلاس داده" منتقل می‌شوند. وقتی این اتفاق می‌افتد یکی از راه حل‌ها، انتقال روالهای استفاده کننده از فیلدها به "کلاس داده" مربوطه است.
2 - کلاس‌های بیش از اندازه صمیمی (Inappropriate Intimacy): کلاس‌ها از اعضای internal یکدیگر بیش از اندازه استفاده می‌کنند. کلاس‌های خوب کلاس‌هایی هستند که کمترین اطلاعی را از وضعیت داخلی یکدیگر دارند. 
3 - کتابخانه‌های ناقص (Incomplete Library Class): زمانیکه کتابخانه‌ای آماده می‌شود، بالاخره روزی می‌رسد که این کتابخانه نیازهای پروژه را رفع نمی‌کند و نیاز به توسعه خواهد داشت. ولی از آنجایی که کتابخانه‌ها به صورت فقط خواندنی در اختیار پروژه‌ها قرار می‌گیرند، در صورتیکه توسعه دهنده اصلی آن از توسعه کتابخانه سر باز بزند، مشکلاتی بوجود خواهد آمد. 
4 - زنجیره فراخوانی‌ها (Message chain): زمانیکه یک متد در بدنه خود پیامی به شیء دیگری می‌فرستد که آن شیء نیز به خودی خود پیامی به شیء دیگری می‌فرستد (و الی آخر) یک زنجیره فراخوانی بوجود آمده است. در این روش بیرونی‌ترین استفاده کننده از متد در واقع وابسته به یک زنجیره‌ای از فراخوانی‌ها است که تغییر در هر قدمی از آن باعث خرابی خواهد شد. 
5 - دلال (Middle man): اگر کلاسی تنها کاری که انجام می‌دهد انتقال فراخوانی به کلاس دیگری است، دیگر نیازی به این کلاس وجود نخواهد داشت.

اطلاع از الگوهای بد کد نویسی به همان اندازه اطلاع از الگوهای خوب کد نویسی در کیفیت محصول تولیدی اثر مثبت خواهند داشت. یادگیری طبقه بندی شده این الگوها کار را برای استفاده روزمره از آنها آسان‌تر خواهد کرد.
  • #
    ‫۷ سال و ۳ ماه قبل، سه‌شنبه ۹ خرداد ۱۳۹۶، ساعت ۱۸:۵۳
    می‌تونید راه حل بهتری در مورد این دو سطر ارائه بدید (چون به عنوان code smell نامبرده شدن)؟
    «استفاده از constant‌ها برای کد کردن اطلاعات مانند USER_ADMIN_ROLE = 1 
    استفاده از constant‌های رشته‌ای به عنوان نام فیلدها در آرایه‌های داده»
    در کل توصیه شده بجای استفاده از magic numbers از ثوابت استفاده بشه. مثلا بجای نوشتن if(role==1) بهتر هست نوشته بشه if(role==UserAdminRole) که از هر لحاظ خوانایی بهتری داره نسبت به ذکر عدد 1 که مشخص نیست چی هست. بعلاوه مشکل تغییر این اعداد هم در کل پروژه صرفا با تغییر محل اصلی اون‌ها قابل حل است و نگهداری رو ساده‌تر می‌کنند.
    • #
      ‫۷ سال و ۳ ماه قبل، سه‌شنبه ۹ خرداد ۱۳۹۶، ساعت ۱۹:۴۳
      معمولا در موارد این چنینی که ذکر شده از  enum‌ها استفاده می‌کنن. ولی بیشتر بستگی به تکه کدی داره که نوشته شده. معمولا میتونیم به روشی ریفکتور انجام بدیم و از پایه نیازی به مثلا چک کردن رول پیش نیاد. ریفکتور‌های مختلفی مورد به مورد میشه استفاده کرد
      • #
        ‫۷ سال و ۳ ماه قبل، شنبه ۱۳ خرداد ۱۳۹۶، ساعت ۰۱:۳۳
        به صورت کلی استفاده  enum‌ها به تنهایی پیشنهاد نمیشود چون enum نشان دهنده یک وضعیت ( state ) است و ما در برنامه نویسی شی گرا نیاز به  وضعیت و رفتار ( state & behavior) داریم و یکی از مشکلات اصلی enum‌ها این است که قابلیت refactoring رو به خوبی ندارن به همین دلایل ما enum‌ها رو در یک کلاس قرار می‌دهیم مانند این مثال:
        sealed class DeviceStatus : IEquatable<DeviceStatus>
            {
                [Flags]
                private enum StatusRepresentation
                {
                    AllFine = 0,
                    NotOperational = 1,
                    VisiblyDamaged = 2,
                    CircuitryFailed = 4
                }
        
                private StatusRepresentation Representation { get; }
        
                private DeviceStatus(StatusRepresentation representation)
                {
                    this.Representation = representation;
                }
        
                public static DeviceStatus AllFine() =>
                    new DeviceStatus(StatusRepresentation.AllFine);
        
                public DeviceStatus WithVisibleDamage() =>
                    new DeviceStatus(this.Representation | StatusRepresentation.VisiblyDamaged);
        
                public DeviceStatus NotOperational() =>
                    new DeviceStatus(this.Representation | StatusRepresentation.NotOperational);
        
                public DeviceStatus Repaired() =>
                    new DeviceStatus(this.Representation & ~StatusRepresentation.NotOperational);
        
                public DeviceStatus CircuitryFailed() =>
                    new DeviceStatus(this.Representation | StatusRepresentation.CircuitryFailed);
        
                public DeviceStatus CircuitryReplaced() =>
                    new DeviceStatus(this.Representation & ~StatusRepresentation.CircuitryFailed);
        
                public override int GetHashCode() => (int)this.Representation;
        
                public override bool Equals(object obj) => this.Equals(obj as DeviceStatus);
        
                public bool Equals(DeviceStatus other) =>
                    other != null && this.Representation == other.Representation;
        
                public static bool operator ==(DeviceStatus a, DeviceStatus b) =>
                    (object.ReferenceEquals(a, null) && object.ReferenceEquals(b, null)) ||
                    (!object.ReferenceEquals(a, null) && a.Equals(b));
        
                public static bool operator !=(DeviceStatus a, DeviceStatus b) => !(a == b);
            }
        اگه دقت کرده باشید این کلاس به صورت value object طراحی شده است و از مهمترین ویژگی اینگونه کلاس‌ها این که تغییرناپذیر هستند. 
        • #
          ‫۷ سال و ۳ ماه قبل، شنبه ۱۳ خرداد ۱۳۹۶، ساعت ۰۲:۱۸
          چرا enum قابلیت refactoring خوبی ندارد؟  با این بسته بندی enum در یک کلاس و بالا بردن درجه‌ی پیچیدگی کد، دقیقا چه ارزش افزوده‌ای را خلق کردید؟ بحث حالت و رفتار باید در کلاس استفاده کننده‌ی از نوع enum مطرح شود یا در کلاسی که یک نوع enum را مجددا بسته بندی کرده؟
          با این بسته بندی این مشکلات را ایجاد کرده‌اید:
          - یک کد ساده تبدیل به یک کد پیچیده شده‌است.
          - عبارات switch با حالت enum-as-class کار نمی‌کند.
          - مشکلات serialization و عکس آن‌را سبب خواهید شد.
          • #
            ‫۷ سال و ۳ ماه قبل، شنبه ۱۳ خرداد ۱۳۹۶، ساعت ۱۲:۵۷
            دستورات  if ، switch مربوط به دوران زبان برنامه نویسی ساخت یافته است و مهمترین فرق بین زبان‌های شی گرا با ساخت یافته وجود dynamic dispatch است و این قابلیت به ما امکان می‌دهد که در زمان اجرا از حالتی به حالتی دیگر برویم. در اینجا ما پیچیدگی کد رو افزایش داده ولی این پیچیدگی دارای ارزش افزوده ای است که مهمترین آن این است که دیگر با object سرو کار داریم نه یک وضعیت.
            عبارت switch با حالت enum-as-class کار نمیکند درست است زیرا آن زمان  switch را برای object‌ها طراحی نکرده بود و استفاده از switch یعنی هاردکد کرد برنامه و ما رو با مشکلات نگهداری و همچنین احتمال بروز باگ رو در برنامه افزایش میدهد.در این مقاله اشاره ای به حذف switch کرده ولی به خوبی مزایایی آن رو توضیح نداده است و یکی از این مزایا این است که ما حالات شرطی رو می‌توانیم بر اساس یک شی به کلاس استفاده کننده تزریق کنیم و مشکلات نگهداری برنامه کمتر شود. 
            • #
              ‫۷ سال و ۳ ماه قبل، شنبه ۱۳ خرداد ۱۳۹۶، ساعت ۱۳:۳۳
              اگر if ، switch  ربطی به یک زبان شیءگرا نداشتند، اصلا در آن قرار نمی‌گرفتند. اگر میزان پیچیدگی یک برنامه افزایش پیدا کند، قطعا نگهداری آن مشکل‌تر خواهد شد. درک آن توسط سایر اعضایی که آن کد را ننوشته‌اند مشکل‌تر خواهد شد. همچنین نیاز به مستندات بیشتری خواهد داشت.
              استفاده از switch یعنی طراحی دقیق برنامه. یعنی حالت‌های مختلف مورد استفاده مشخص هستند و تفکری در پشت طراحی یک برنامه وجود داشته. اما زمانی هست که می‌خواهید به استفاده کننده‌ی از یک API امکان مانور بیشتری را بدهید تا بتواند قطعاتی را به برنامه متصل کند. در این حالت بهتر است از الگوی استراتژی بجای استفاده از روش‌هایی که تقریبا در هیچ پروژه‌ی مطرحی استفاده نمی‌شود، کمک گرفت. بنابراین باید دقت داشت که آیا ما درحال طراحی یک قطعه کد برای عموم هستیم یا یک برنامه‌ی مشخص با طراحی کاملا دقیق.
  • #
    ‫۷ سال و ۳ ماه قبل، پنجشنبه ۱۱ خرداد ۱۳۹۶، ساعت ۲۳:۰۰
    اشاره کردید :"3 - کلاس داده (ِData class):  کلاس‌هایی که تنها فیلدهای اطلاعاتی در آنها وجود دارند و متدهای خامی که جهت دریافت یا ذخیره اطلاعات در آنها استفاده می‌شوند. "
    حین کار با هر ORM ای  ، بسته به اندازه پروژه تعداد زیادی از چنین کلاس هایی وجود خواهد داشت. حدس میزنم من دقیقا متوجه منظورتون نشده باشم. اگه بیشتر توضیح بدید ممنون میشم.
  • #
    ‫۷ سال و ۳ ماه قبل، یکشنبه ۲۱ خرداد ۱۳۹۶، ساعت ۱۵:۲۶
    تصحیح مطلب
    در بخش مربوط به "بد استفاده کنندگان از شی گرایی" مورد دوم در مطلب به عنوان "Refused request" یا "درخواست رد شده" مطرح شده که نادرست است. لفظ درست مربوط به این بوی بد کد "Refused Bequest" یا "میراث رد شده" است.