הבלוג של ינון פרק

טיפים קצרים וחדשות למתכנתים

למה קשה ללמוד ריאקט נייטיב

30/07/2022

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

// from: https://raw.githubusercontent.com/flatlogic/react-native-starter/master/src/modules/pages/PagesView.js

import React from 'react';
import { StyleSheet, View, Text, TouchableOpacity, Image } from 'react-native';

import { colors, fonts } from '../../styles';

const chartIcon = require('../../../assets/images/pages/chart.png');
const calendarIcon = require('../../../assets/images/pages/calendar.png');
const chatIcon = require('../../../assets/images/pages/chat.png');
const galleryIcon = require('../../../assets/images/pages/gallery.png');
const profileIcon = require('../../../assets/images/pages/profile.png');
const loginIcon = require('../../../assets/images/pages/login.png');
const blogIcon = require('../../../assets/images/pages/blog.png');

export default function PagesScreen(props) {
  return (
    <View style={styles.container}>
      <View style={styles.row}>
        <TouchableOpacity
          onPress={() => props.navigation.navigate('Charts')}
          style={styles.item}
        >
          <Image
            resizeMode="contain"
            source={chartIcon}
            style={styles.itemImage}
          />
          <Text style={styles.itemText}>Charts</Text>
        </TouchableOpacity>
        <TouchableOpacity
          onPress={() => props.navigation.navigate('Gallery')}
          style={styles.item}
        >
          <Image
            resizeMode="contain"
            source={galleryIcon}
            style={styles.itemImage}
          />
          <Text style={styles.itemText}>Gallery</Text>
        </TouchableOpacity>
        <TouchableOpacity
          onPress={() => props.navigation.navigate('Profile')}
          style={styles.item}
        >
          <Image
            resizeMode="contain"
            source={profileIcon}
            style={styles.itemImage}
          />
          <Text style={styles.itemText}>Profile</Text>
        </TouchableOpacity>
      </View>
      <View style={styles.row}>
        <TouchableOpacity
          onPress={() => props.navigation.navigate('Chat')}
          style={styles.item}
        >
          <Image
            resizeMode="contain"
            source={chatIcon}
            style={styles.itemImage}
          />
          <Text style={styles.itemText}>Chats</Text>
        </TouchableOpacity>
        <TouchableOpacity
          onPress={() => props.navigation.navigate('Calendar')}
          style={styles.item}
        >
          <Image
            resizeMode="contain"
            source={calendarIcon}
            style={styles.itemImage}
          />
          <Text style={styles.itemText}>Calendar</Text>
        </TouchableOpacity>
        <TouchableOpacity
          onPress={() => props.navigation.navigate('Auth')}
          style={styles.item}
        >
          <Image
            resizeMode="contain"
            source={loginIcon}
            tintColor={colors.primary}
            style={styles.itemImage}
          />
          <Text style={styles.itemText}>Login</Text>
        </TouchableOpacity>
      </View>
      <View style={styles.row}>
        <TouchableOpacity
          onPress={() => props.navigation.navigate('Blog')}
          style={styles.blogItem}
        >
          <Image
            resizeMode="contain"
            source={blogIcon}
            tintColor={colors.primary}
            style={styles.itemImage}
          />
          <Text style={styles.itemText}>Blog</Text>
        </TouchableOpacity>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: colors.white,
    paddingTop: 10,
  },
  row: {
    flexDirection: 'row',
    paddingHorizontal: 10,
    marginTop: 10,
  },
  item: {
    flex: 1,
    height: 120,
    paddingVertical: 20,
    borderColor: colors.primaryLight,
    borderWidth: 1,
    borderRadius: 5,
    alignItems: 'center',
    justifyContent: 'space-around',
    marginHorizontal: 5,
  },
  blogItem: {
    width: '31%',
    height: 120,
    paddingVertical: 20,
    borderColor: colors.primaryLight,
    borderWidth: 1,
    borderRadius: 5,
    alignItems: 'center',
    justifyContent: 'space-around',
    marginHorizontal: 5,
  },
  itemText: {
    color: colors.primary,
    fontFamily: fonts.primary,
  },
  itemImage: {
    height: 35,
  },
});

למרות שהטכנולוגיה בגדול מופלאה ועובדת, ללמוד ריאקט נייטיב יכול להיות משימה לא פשוטה ממספר סיבות:

  1. בגלל שמכונות היעד הן טלפונים, כדאי שיהיו לנו כמה טלפונים לבדיקה והיכרות לפחות בסיסית עם המושגים בפיתוח מובייל ובנוסף עם סביבות הפיתוח XCode ו Android Studio. זה לבד הרבה חומר ללמוד.

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

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

  4. עיצוב ריאקט נייטיב, למרות שנראה במבט ראשון כמו CSS (או יותר נכון כמו CSS In JS), עדיין דורש למידה ובדיקה גם לאנשים שמכירים טוב CSS כי לא כל המאפיינים נתמכים או נתמכים בכל הקומפוננטות.

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

העלות של קוד חיצוני

29/07/2022

גירסה 14 של user-event יצאה עם עדכון לממשק של הספריה. בגירסאות קודמות היה אפשר לכתוב:

userEvent.click(screen.getByText('Check'))

ואחרי שידרוג לגירסה 14 אנחנו צריכים להוסיף await לפקודה:

await user.click(screen.getByRole('button', {name: /click me!/i}))

זה השינוי הכי קל בעולם - רק צריך למצוא כל מקום שהשתמשת בספריה ולהוסיף await לפניו. ובכל זאת קשה לי לדמיין מתכנתת שקמה בבוקר ואומרת "היום אני הולכת לשדרג את user-event ולעדכן את כל הטסטים שלי, פשוט כי בא לי לשדרג". בכל עבודה על מערכת, או שיש עכשיו באגים קריטיים שצריך לפתור, או שיש פיצ'רים דחופים שצריכים להגיע ללקוחות, או שאין לקוחות ואז זה עוד יותר מלחיץ וצריך דחוף להתקדם בפיתוח כדי שמישהו ישלם על העבודה. אף פעם אין זמן טוב לעדכן את כל הבדיקות רק בשביל לשדרג ספריה.

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

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

ללמוד לקבל מגבלות

28/07/2022

נניח שהגעתם לפרויקט ריאקט ויש בו עץ קומפוננטות מורכב שנגמר באיזה input, משהו כזה רק דמיינו שכל קומפוננטה יושבת בקובץ אחר והרבה יותר מורכבת:

function A(props) {
    return <B />
}

function B(props) {
    return <C />
}

function C(props) {
    return <D />
}

function D(props) {
    return <input type="text" />
}

ועכשיו אני רוצה להוסיף checkbox בקומפוננטה A שיגרום לתיבת הטקסט ב D להיות disabled. בואו נעשה את זה עוד יותר מעניין ונדמיין ש C ו D בכלל נכתבו בפרויקט אחר ויש עוד כמה עשרות פרויקטים שמשתמשים בהם.

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

בריאקט בשביל שקומפוננטה A תשפיע על קומפוננטה D אני צריך להעביר סטייט ביניהן - כלומר אני צריך להגדיר משתנה סטייט ב A, לשלוח אותו ל B, שתשלח אותו ל C, שתשלח אותו ל D שבתורה צריכה להעביר אותו ל input. ותקוו שיש לכם מספיק קפה בשביל כל הריפקטורינג הזה.

אבל בעצם אני יודע ריאקט. ואני יכול לרמות. אני יכול לכתוב קוד ב A שיצלול פנימה ל D וישנה את מצב ה disabled של התיבה. בשבילכם אפילו כתבתי את זה:

import "./styles.css";
import { useState, useEffect, useRef } from "react";

export default function App() {
  const [disabled, setDisabled] = useState(false);
  const el = useRef(null);

  function handleChange(e) {
    if (e.target.checked) {
      setDisabled(true);
    } else {
      setDisabled(false);
    }
  }

  useEffect(() => {
    const checkbox = el.current.querySelector('input[type="text"]');
    checkbox.disabled = disabled;
  }, [disabled]);

  return (
    <div className="App" ref={el}>
      <h1>Hello CodeSandbox</h1>
      <label>
        <input type="checkbox" checked={disabled} onChange={handleChange} />
        Disabled
      </label>
      <A />
    </div>
  );
}

function A(props) {
  return <B />;
}

function B(props) {
  return <C />;
}

function C(props) {
  return <D />;
}

function D(props) {
  return <input type="text" />;
}

והנה אפילו סנדבוקס עם הקוד עובד: https://codesandbox.io/s/throbbing-snowflake-u6gv87?file=/src/App.js

מה שמחזיר אותנו לכותרת.

העלות הכי גבוהה בפיתוח היא לא פיתוח הפיצ'רים אלא תחזוקה שלהם. ריפקטורינג לכל הקומפוננטות זה סיפור יקר, אבל אחריו כולם זוכרים שקומפוננטות יכולות לעבוד גם במצב Disabled ולוקחים את זה בחשבון בכל פיתוח עתידי. כשאני עוקף את המגבלות בשביל להגיע למוצר עובד מהר יותר, אני רק מכניס מוקשים לקוד. כל שינוי עתידי קטן בקוד ישבור את הקוד שלי בלי שום אזהרה, ובלי שמי שמעדכן את D, C או B בכלל יודע שהוא שבר משהו.

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

מדריך sed

27/07/2022

משמעות השם sed היא Stream Editor, ותפקידו של sed בהתאם: הוא עורך טקסט שמקבל את הקלט שלו בתור זרם (מקובץ או מ Pipeline) ועורך את הטקסט שמגיע לפי הוראות שהעברנו מראש. במדריך זה אספר על סד, קודם התיאוריה ואז דרך דוגמאות.

המשך קריאה

מבחן התוצאה

26/07/2022

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

אצל ענת המחשב היה חדש והאינטרנט מהיר. היא הלכה לפי מדריך ההתקנה והכל עבד. תוך 8 דקות הסביבה הותקנה והתוכנית הראשונה כבר עבדה.

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

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

האמת כמובן יותר מורכבת.

אף אחד לא רוצה שהסביבה תעבוד נגדו. אף אחד לא רוצה להתמודד עם שלושה לילות ללא שינה כדי לפענח הודעות שגיאה חסרות משמעות בסקריפטים שמחר כבר לא נצטרך אותם. אבל כשזה כבר קורה (ובתחום שלנו זה קורה הרבה), שווה לשים לב לרווחים שבצלילה כזאת: להבנה היותר מעמיקה איך הסביבה עובדת; לתרגול התמודדות עם מצבים הזויים; להיכרות עם המערכת בעומקים שלא תכננת להגיע אליהם, היכרות שתעזור גם בהמשך כשהודעות שגיאה דומות יופיעו בעולם האמיתי.

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

שינוי גישה

25/07/2022

למרות כמה אילוצים של השפה, אין שום בעיה לכתוב קוד C בשפת Java; אין שום בעיה לכתוב בדיקות שלא בודקות כלום, גם עם 100% כיסוי קוד; וראיתי מספיק תוכניות jQuery שבמקרה כתובות בריאקט.

שינוי טכנולוגיה לא מבטיח שינוי גישה. זה שינוי הגישה שהוא המפתח לשינוי הטכנולוגי.

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

  1. בדיקות יחידה.

  2. קוד "גנרי" שכתבתי בשביל הגישה הישנה, אבל עכשיו רק מעכב אותי.

  3. סקריפטים שהתאימו ל Workflow מסוים, אבל אף אחד לא יכול לקרוא או לעדכן אותם.

  4. והכי קשה - תפיסת עולם ודרכי עבודה שהיו נכונות בגישה הישנה אבל עכשיו כבר לא.

אפקטים וקוד איתחול בריאקט 18

24/07/2022

הרבה זמן המהנדסים שעובדים על ריאקט עשו מאמץ לא לשבור כלום. כשהיה צריך דרך חדשה לעשות דברים - למשל כשנכנסו Hooks לתמונה - זה תמיד היה בנוסף לדרך הקיימת ולא במקום. אפילו את תחביר המיקסינים הישן אפשר היה להפעיל בעזרת ספריה חיצונית שנים אחרי שהתמיכה המובנית בו בוטלה.

בגירסה 18 נראה שהמאמץ הזה הסתיים.

מנגנון Concurrent Mode שובר את הרעיון ש render של כל הקומפוננטות מתבצע "במכה אחת", ולכן פותח דלת לבאגים של סינכרון בין render-ים של קומפוננטות שונות. יכולים לקרוא על הבעיה כאן.

מנגנון נוסף שנשבר הוא useEffect, או יותר ספציפית השימוש ב useEffect כדי לבצע פעולה חד-כיוונית כשמשהו קורה. הנה דוגמה מתוך התיעוד של expo, ספריית פיתוח שעוטפת את react native:

import React, { useEffect } from 'react';
import { StyleSheet, View, Text } from 'react-native';
import * as Brightness from 'expo-brightness';

export default function App() {
  useEffect(() => {
    (async () => {
      const { status } = await Brightness.requestPermissionsAsync();
      if (status === 'granted') {
        Brightness.setSystemBrightnessAsync(1);
      }
    })();
  }, []);

  return (
    <View style={styles.container}>
      <Text>Brightness Module Example</Text>
    </View>
  );
}

הקוד המקורי בקישור: https://docs.expo.dev/versions/v45.0.0/sdk/brightness/#brightnessrequestpermissionsasync

המטרה של האפקט היא לבקש מהמשתמש גישה לשינוי רמת הבהירות של התצוגה - פעם אחת ורק כשהאפליקציה עולה. יש פה הנחה שהקומפוננטה הראשית תעשה mount פעם אחת בלבד כשהאפליקציה עולה ולעולם לא תעשה unmount.

וזאת בדיוק ההנחה שנשברת בריאקט 18: הנחת העבודה של ריאקט 18 (והלאה) היא שקומפוננטות יכולות תמיד לצאת מהמסך ולהיכנס חזרה. ריאקט שומר לעצמו את הזכות להוציא ולהכניס קומפוננטות למסך כשזה יתאים לו, ומצב הפיתוח של ריאקט 18 מבהיר את זה כשב Strict Mode אוטומטית כל קומפוננטה עוברת unmount ואז שוב mount כשהיא נכנסת למסך.

במבט קדימה בעבודה עם ריאקט וכדי לא לשבור דברים נרצה לכוון לעבוד עם אפקטים אך ורק כדי לסנכרן בין קומפוננטה למשאב חיצוני - כך שקוד האפקט מפעיל איזושהי פעולות subscribe ומחזיר פונקציה שעושה unsubscribe; את קוד האיתחול של המערכת שבאמת צריך לקרות רק פעם אחת, לדוגמה קוד שמבקש הרשאות, נצטרך להתחיל להריץ מחוץ לקומפוננטות בקובץ איתחול מסודר.

הודעת השגיאה

23/07/2022

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

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

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

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

עבודה יפה בכיוון הלא נכון

22/07/2022

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

אני יכול לפתח תשתית בדיקות יחידה מטורפת למערכת שכוללת מעט לוגיקה והרבה קריאות למערכות חיצוניות. אני אכתוב אינסוף Mock-ים לאינסוף סריביסים חיצוניים, ובסוף כל התשתית היפה והבדיקות לא יגלו אף באג.

אני יכול לבנות Design System חדש מאפס רק בשביל שיהיה ממשק יפה לאיזה כלי פנימי שאני כותב, אבל תכל'ס גם עם antd הייתי מקבל בדיוק את אותו פידבק מהלקוחות.

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

המשחק הוא התאמה - לבנות פיתרון יפה שלוקח בחשבון את הבעיה ומביא תוצאות.

לא שווה את המאמץ

21/07/2022

יש שלוש נקודות בפרויקט בהן אפשר לקבל את התחושה שהפרויקט "לא שווה את המאמץ": בהתחלה, באמצע ובסוף.

אם לפני הפרויקט הרגשת שהפרויקט לא שווה את המאמץ, אז אפשר לעצור פה. כנראה שהפרויקט לא יתקדם.

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

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

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

אחרי בירור ברשת גילית שיש סיכוי טוב שהטטריס יהיה מוכן תוך חודשיים של לימוד ועבודה בהשקעה של שעה-שעתיים ביום, והחלטת לצאת לדרך.

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

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