• בלוג
  • יותר טוב מ ChatGPT - תרגיל שיפור קוד בפייתון

יותר טוב מ ChatGPT - תרגיל שיפור קוד בפייתון

19/11/2025

נתון הקוד הבא:

import sys

def print_1(text):
    print(f"*{text}*")

def print_2(text):
    print(text)

def print_3(text):
    print(f"# {text}")

def handle(output_type, text):
    method_name = f"print_{output_type}"
    globals()[method_name](text)

if __name__ == "__main__":
    handle(*sys.argv[1:])

הסבירו מה הבעיות בו מבחינת קריאות ושפרו את הקוד מבלי לשנות את ההתנהגות שלו ואת אופן הפעלתו.

1. צעד 1 - שמות הפונקציות

הבעיה הראשונה שקופצת לעין היא השמות של הפונקציות. אנחנו לא אוהבים לקרוא לפונקציות בשם גנרי ואז מספר אלא בשם שמסביר מה הפונקציה עושה, לכן הדבר הראשון שנעשה הוא לתקן את השמות של הפונקציות.

הבעיה היא ששינוי שם הפונקציות מכריח אותנו לוותר על ההפעלה ה"גנרית" שלהן ולכן נקבל גירסה יותר ארוכה של הקוד:

import sys

def print_italics(text):
    print(f"*{text}*")

def print_body(text):
    print(text)

def print_header(text):
    print(f"# {text}")

def handle(output_type, text):
    match output_type:
        case "1":
            print_italics(text)

        case "2":
            print_body(text)

        case "3":
            print_header(text)


if __name__ == "__main__":
    handle(*sys.argv[1:])

2. צעד 2 - מידול המיפוי

הסקריפט מקבל מספר וטקסט ומדפיס את הטקסט לפי המספר. זה אולי לא מושלם אבל בתרגיל ביקשתי לא לשנות את החתימה של הקוד. עדיין כשהמיפוי "מוחבא" בתוך הפונקציה handle יהיה לי קשה בעתיד למצוא אותו, במיוחד ככל שהפרויקט יגדל. צעד טוב קדימה יהיה להגדיר את המיפוי בצורה יותר מסודרת, או בקלאס או בפונקציה. אחרי השינוי אני מקבל:

import sys

def print_italics(text):
    print(f"*{text}*")

def print_body(text):
    print(text)

def print_header(text):
    print(f"# {text}")


class OutputTypeMapping:
    mapping = {
            "1": print_italics,
            "2": print_body,
            "3": print_header
            }

    @staticmethod
    def get_handler_for_code(code):
        return OutputTypeMapping.mapping[code]


def handle(output_type, text):
    OutputTypeMapping.get_handler_for_code(output_type)(text)

if __name__ == "__main__":
    handle(*sys.argv[1:])

3. סקירה ומחשבות קדימה

כשמסתכלים על הגירסה השלישית מיד עולה השאלה "למה לא לשים את הכל בקלאס אחד", כלומר למה לא לכתוב קלאס של MarkdownFormatter שיחזיק גם את המיפוי וגם את פונקציות ההדפסה. התשובה שלי היא קודם כל שזה לא יתרום לקריאות של הקוד, הגירסה האחרונה כבר תיקנה את הבעיה הכי מרכזית בקוד הקודם וכבר די קריאה, ויותר מזה שינוי כזה עלול להקשות אם נרצה בעתיד להוסיף עוד Formatter, אולי הדפסה ל HTML. ההפרדה בין "מיפוי קוד לפעולה" לבין מימוש הפעולות היא הפרדה הגיונית.

מחשבה שניה נוגעת ליכולת של כלי AI להתמודד עם תרגילים כאלה. ג'יפיטי 5.1 השיב לתרגיל את הגירסה הבאה:

import sys

def print_1(text):
    print(f"*{text}*")

def print_2(text):
    print(text)

def print_3(text):
    print(f"# {text}")

PRINT_HANDLERS = {
    "1": print_1,
    "2": print_2,
    "3": print_3,
}

def handle(output_type, text):
    try:
        handler = PRINT_HANDLERS[output_type]
    except KeyError:
        raise KeyError(f"Unknown output type: {output_type}")  # שגיאה ברורה יותר
    handler(text)

if __name__ == "__main__":
    handle(*sys.argv[1:])

וגם Gemini וגם קלוד שמרו את השמות של הפונקציות עם המספרים. ככל שכלים אלה הולכים ללוות אותנו בשנים הקרובות חשוב לשים לב להנחות אותם ולקבוע בעצמנו את ה Coding Standards ומבנה הקוד כי בסוף אנחנו אלה שנצטרך לתחזק את המערכות שהם בונים.