יש מעט מאוד מתכנתים טובים. הסיבה היא שהמון ידע הכרחי פשוט לא נלמד באופן פורמלי. הלימודים מתמקדים בתוצאות מיידיות ואינם מתייחסים לתהליכים או תחזוקה. בעוד אורך החיים של הפרויקט הלימודי הממוצע הוא ימים, בתעשייה פרוייקטים חיים שנים ארוכות. כנ"ל לגבי המורכבות והגודל. בחלק מהחברות, ובמיוחד בסטארטאפים, הדרישות עלולות להשתנות ללא הרף. התוכנה צריכה להיות גמישה מספיק כדי לתמוך בקלות בכל גחמה של לקוח או איש מוצר. כדי להצליח לבנות מערכות טובות באופן אפקטיבי בסביבות כאלה נדרש סט כלים שונה בתכלית מזה הנלמד. ובפועל הוא נרכש תוך כדי עבודה על ידי מעטים שיש להם את התשוקה והמוטיבציה להצטיין.
מתכנת טוב תמיד שואף להנמיך את רמת המורכבות של התוכנה.[1] הסיבה היא שסיבוכיות גדלה באופן אקספוננציאלי[2], ואם לא נאלף אותה, הפיתוח ייעצר ונאלץ להשקיע את רוב זמננו בתיקון באגים. דרך אחת להתמודד עם מורכבות היא נסיון מתמיד להנמיך את מספר המצבים שבהם התוכנה יכולה להימצא.[3] אך הכלי האפקטיבי ביותר לעשות זאת, כפי שאמר לנו דייקסטרה ב-1972, הוא ההפשטה[4]. זו הדרך היחידה שבה ניתן לתאר בצורה יעילה פונקציונליות מורכבת עם מספר מצבים גבוה. זו הסיבה שאנחנו מזמינים מנות מתפריט ולא מסבירים למלצר את הוראות ההכנה שלהן. למשל, כולם מבינים מהי מחסנית, וכך נחסך תיאור מייגע על ההתנהגות של מבנה הנתונים הזה עבור כל אחת מהפעולות שהוא מאפשר.
תכנות הוא גם מדע וגם אמנות. הפן המדעי כולל הנחת הנחות, ביצוע מדידות, ולבסוף הוכחה או הפרכה. כך הלוך ושוב עד להתכנסות ל"אמת". אך תכנות הוא גם מקצועי יצירתי. קשה יותר ליישם את השיטה כאשר ישנן אינסוף דרכים לסדר מערכת, אינסוף דרכי מידול מידע, ומספר גבוה של טכנולוגיות לבחור מהן. לכל בחירה יתרונות וחסרונות. כמעט בלתי אפשרי להכיר טכנולוגיה מבלי לעבוד איתה קודם לכן. כך החלטות רבות מתקבלות ללא נתונים, באופן שרירותי, על בסיס תחושת בטן. טעם ואסתטיקה (בעיני זה יותר יפה) כוחו של הרגל (אני רגיל לעבור בדוט נט), או סתם גחמות אישיות (זה מרגיש לי יותר טוב ככה) משפיעים רבות על התוצאה הסופית. שני אנשים יכולים לבנות שתי מערכות שפותרות את אותה הבעיה אבל הן יהיו שונות אחת מהשניה.[5]
יעילות היא כמעט תמיד לא חשובה. אין חן בלכתוב הכל "יעיל" – ברוב המקרים הוא פשוט הופך ל"לא קריא". האתגר הוא לכתוב תוכנה נכונה.[6] במידה ויש בעיה של ביצועים שמפריעה ללקוח, חוזרים ומייעלים באופן נקודתי את צווארי הבקבוק. היוצא מן הכלל הוא שלב התכנון: אם לא נפרק את הבעיה לרכיבים בלתי-תלויים, או אם נבחר את הטכנולוגיות הלא מתאימות, הביצועים שלנו יהיו כבולים לעד לבחירות הגרועות שלנו. שינויי קוד יכולים לשפר את המהירות עד כדי. עבודת הביצועים המשמעותית ביותר מתרחשת בשלב התכנון.
כלים משנים. כמו הנגר כשגילה את המסור החשמלי, המתכנת שמכיר את כלי העבודה החזקים ביותר ויודע לשלוט בהם ביד רמה הוא יותר פרודוקטיבי.[7] אני מדבר בין היתר על שפת התכנות, עורכי טקסט, כלי ניהול גירסה (למשל גיט), והיום גם כל עוזרי ה-AI למינהם. כמו הנגר, עליכם לדעת לבחור את הכלים החזקים ביותר[8] עבור המשימה שלכם. הפיכה למומחים לשימוש בכלים האלה עושה הבדל גדול.
שימוש בטרמינל הוא מכפיל כח. הסיבה היא שבניגוד לממשק גרפי, הוא מאפשר חזרה על פעולות. הוא הופך את כל הפעולות לכאלה שניתן לשרשר, להריץ ברקע, ולשלב באוטומציות בקלות רבה. היסטוריה של הטרמינל מאפשרת הרצה חוזרת של כל הפעולות שהרצתם אי פעם, וחיפוש לאחור יאפשר לכם למצוא אותן בקלות[9].
התעלמות מאזהרות הקומפיילור היא אחד מסימני ההיכר של החובבנות. באגים רבים יכולים להימנע פשוט על ידי הקשבה לאזהרות האלה. הן גם פותחות צוהר להבנה יותר עמוקה של שפת התכנות (כלומר, אחד מכלי העבודה העיקריים שלכם). אם אזהרה לא רלוונטית למקרה ספציפי או לתוכנה ספציפית, דכאו אותה באופן מפורש והסבירו למה היא לא רלוונטית.
ירושה היא לפולימורפיזם. כלומר הצורך להעביר אובייקטים במערכת מבלי לדעת את המימוש שלהם. תנאי מקדים לכך הוא הפשטה איכותית והגדרה מוצלחת של הממשק המשותף, וקיומו של יותר מיוז קייס אחד[10]. ברוב המקרים, התנאים האלה לא מתקיימים, ולכן אין באמת צורך בירושה. בשביל שיתוף פונקציונליות, העדיפו קומפוזיציה על ירושה[11]. בשביל פולימורפיזם, העדיפו ירושת ממשקים על ירושת אובייקטים.
הערות הן כישלון להתבטא באמצעות קוד – כך אמר לנו הדוד בוב[12]. יש שהסיקו מכך שקוד טוב מתעד את עצמו ואינו זקוק לתיעוד. אבל זה בלבול מוח. נכון: כשהקוד מתפתח, הערות נוטות להתיישן ובהמשך להטעות. זו הסיבה שכדאי לשאוף שלא להשתמש בהן. מובן שאם ניתן לבטא את רעיון באמצעות שמות של משתנים, פונקציות, אובייקטים או מבנה, עשו זאת. אבל לעיתים נדרשים הסברים ברמת הפשטה גבוהה יותר שלא ניתן לספק באמצעות הקוד. למשל כשנעשה דבר מוזר ולא שגרתי, או כשמממשים אלגוריתם מוכר, וקשה להבין זאת מהקוד עצמו. הערות הן חובה בתיעוד ממשקים: מהו קלט תקין, אילו אקספנש ייזרקו באילו תנאים וכ'. כל הקונספט של תלות בממשקים ולא במימושים נשען על תיעוד איכותי: בלעדיו, ובלי להיות חשופים למימוש, פשוט אין לדעת מה יקרה כשנשתמש בהם.
יחסי אנוש הרבה יותר חשובים ממה שמניחים. תכנות הוא מקצוע קולבורטיבי. מעט מאוד מהפרוייקטים הרציניים נבנים על ידי מתכנת בודד. שיתוף פעולה אפקטיבי דורש יחסי אנוש מעולים. מתכנת שלא מסוגל לתקשר בבהירות, לקבל ולהעביר ביקורת, להתחשב ברגשות, ולהתייחס בכבוד לחבריו לצוות, לא יגיע רחוק.
כנות אינטלקטואלית היא תכונת מפתח. המתכנתים הכי טובים שאני מכיר לא "מתאהבים ברעיון". הם מבינים שכמה מוחות טובים יותר ממוח אחד. הם מזמינים ביקורת, מכיוון שזו עוזרת להם להתכנס אל פתרון מוצלח יותר. הם בוחנים את האלטרנטיבות השונות בצורה יבשה ונטולת אגו.
היטפלות לקטנות זה כח-על. מתכנתים טובים קפדנים ברמה שתעורר אי נוחות. הם לא מהססים לאתגר את החכמה הקונבנציונאלית, במרדף כמעט אובססיבי אחר סתירות. הם בחיים לא יגידו "אני לא יודע למה, אבל זה עובד". כשהם נתקלים בהנחה שכולם מקבלים כמובנית מאליה, הם לא יהססו לבחון אותה מחדש. כך הם מגלים חורים. ובחורים האלה, נמצאות ההזדמנויות. רוב האנשים מפספסים את ההזדמנויות האלה.
לא צריך להיות חכם כדי להיות מתכנת טוב. אף אחד לא באמת מספיק חכם כדי לתכנת מחשבים. מספר המצבים והפעולות בתוכנה הממוצעת גדול מכדי שמוח אנושי יוכל להכיל אותם. המתכנתים הטובים ביותר הם אלה שמכירים במגבלה הזאת ויודעים להכין לעצמם "גלגלי עזר". הקפדה על שמות, הפשטות, כלים, טסטים, סטייל עקבי וצימוד נמוך הם רק חלק מהדוגמאות. במילותיו של דייקסטרה: "נתכנת טוב יותר אם ניגש למשימה תוך כדי יראת כבוד מפני הקושי העצום שלה, היצמדות לשפות תכנות צנועות ואלגנטיות, ותוך הכרה במגבלות המוח האנושי. עלינו לגשת למשימה כמתכנתים ענוים מאוד."[13]
- אינני מחדש כאן. לקריאה נוספת:
- Steve MccConnell, Code Complete, 2nd Edition, 77
- Martin Kleppmann, Designing Data-Intensive Applications, 20
- דרך פשוטה להדגים זאת היא באמצעות תוכנה פשוטה שמורכבת מכמה תנאים, כשכל תנאי מדפיס אות אחרת. מספר המצבים של התוכנה הוא 2 בחזקת מספר התנאים.If (a) { print x } if (b) { print y} if (c) { print z} // can print ‘x’, ‘y’, ‘z’, ‘xy’, ‘xz’, ‘xyz’, ‘yz’ ↑
- למשל, באמצעות שימוש במבני נתונים בלתי משתנים (immutables), שימוש מינימלי באובייקטים וקומפוננטות שיש להם מצב (stateful), הגבלת הממשקים כך שיקבלו כקלט אך ורק את מה שנדרש להם על מנת לבצע את עבודתם, ועוד. ↑
- "We all know that the only mental tool by means of which a very finite piece of reasoning can cover a myriad cases is called “abstraction”; as a result the effective exploitation of his powers of abstraction must be regarded as one of the most vital activities of a competent programmer. In this connection it might be worth-while to point out that the purpose of abstracting is not to be vague, but to create a new semantic level in which one can be absolutely precise.” E.W. Dijkstra, The Humble Programmer ↑
- ניתן כמובן להתווכח על רמת השונות. למערכות דומות יש פעמים רבות ארכיטקטורה דומה. ועדיין קיימים הבדלים שרירותיים ביניהם. למשל, מהי הסיבה שמערכת הקבצים של Windows וזו היוניקסית משתמשות כל אחת מהן בסלאש לכיוון אחר? ↑
- אינני מחדש כאן. דונלד קנות' אמר פעם ש"אופטימיזציה מוקדמת היא שורש כל הרע". למי שמעוניין להתעמק, ג'ושוע בלוך מסביר נהדר:
- Joshua Bloch, Effective Java, 3rd Edition, 286
- לוריאציה של הרעיון הזה נחשפתי לראשונה בספר The Pragmatic Programmer (פרק 3). אסייג ואומר שראיתי מתכנתים מעולים שהיו פרימיטיבים במיוחד כשזה נוגע לכלים. הם יותר מפיצו על כך בחוזקותיהם האחרות. ↑
- כלי חזק הוא כלי שמאפשר לעשות יותר בפחות. מברגה היא כלי חזק יותר ממברג, למשל. ↑
- אני חובב גדול של fzf בטרמינל יוניקסי. ↑
- יש יוצאים מן הכלל: למשל כאשר אנו כותבים ספריה או sdk. אך בקוד פנימי, אם אי פעם נרצה להוסיף מימוש נוסף, ניתן בקלות לעשות חילוץ ממשק – פעולה שכל IDE מודרני יודע לבצע היום באופן אוטומטי. לכן אין שום צורך לכתוב קוד כללי עבור יוזקייס אחד. ↑
- הסבר רהוט במיוחד תוכלו לקרוא כאן:
- Joshua Bloch, Effective Java, 3rd Edition, 87
- Robert C. Martin, Clean Code, 54 ↑
- The Humble Programmer, E.W Dijkstra ↑