fa
Feedback
ToCode

ToCode

رفتن به کانال در Telegram

טיפים קצרים למתכנתים מאת ינון פרק

نمایش بیشتر
1 420
مشترکین
اطلاعاتی وجود ندارد24 ساعت
+27 روز
-230 روز
آرشیو پست ها
ToCode
1 419
# ביצועים ושפות מודרניות אלגוריתם Fisher-Yates לערבוב מערך מציע לבצע את הצעדים הבאים: 1. בחרו אינדקס אקראי במערך. 2. מחקו את האיבר באינדקס שבחרתם וכתבו אותו בסוף הרשימה. 3. המשיכו עד שמחקתם את כל האיברים. זה אלגוריתם אלגנטי וקל למימוש בכל שפה, לדוגמה ב JavaScript:
function shuffle(arr) {
  let end = arr.length;
  while (end >= 0) {
    const nextIndex = Math.floor(Math.random() * end);
    arr.push(arr[nextIndex]);
    arr.splice(nextIndex, 1);
    end -= 1;
  }
  return arr;
}
הבעיה שכמעט בלי לשים לב כתבתי קוד הרבה פחות יעיל ממה שהיה אפשר לכתוב. המחיקה עם slice מכריחה את המחשב להעתיק את כל הערכים במערך מקום אחד אחורה, מה שגורם להמון עבודה מיותרת. במקום splice ו push מימוש מודרני של אותו אלגוריתם משתמש בהחלפה:
function shuffle(arr) {
  for (let i=arr.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * i);
    const item = arr[i];
    arr[i] = arr[j];
    arr[j] = item;
  }

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

ToCode
1 419
# הודעות שגיאה מטעות שגיאה בתוכנה הדפיסה לי את ההודעה הבאה ללוג:
Permissions should be u=rwx (0700).
בהדפסת הודעת שגיאה למשתמשים שווה לשים לב שאנחנו נותנים להם הוראות מדויקות איך לתקן את השגיאה ולהמשיך הלאה. לא זה המצב בשגיאה המודבקת. אם אני לוקח קובץ שיש לו הרשאות לא מתאימות למשל:
$ ls -l b.txt
-rw-rw-r-- 1 ubuntu ubuntu 0 Nov 16 12:39 b.txt
ומריץ את הפקודה:
$ chmod u=rwx b.txt
ההרשאות עדיין יהיו לא מתאימות:
$ ls -l b.txt
-rwxrw-r-- 1 ubuntu ubuntu 0 Nov 16 12:39 b.txt
למעשה אפשר להסיק מהודעת השגיאה שתי פקודות שונות להריץ - האחת, chmod u=rwx, תשנה רק את ההרשאות לבעלים של הקובץ; השניה, chmod 0700, באמת תוריד גם את ההרשאות מכל האחרים. הודעת שגיאה טובה יותר היתה מציגה שתי אפשרויות שקולות, למשל היה עדיף לכתוב:
Permissions should be a=,u=rwx (0700).
או אפילו:
Permissions should be u=rwx,g=,o= (0700).
הודעות שגיאה טובות שוות זהב ויכולות לחסוך למשתמשים שלכם זמן וכאב ראש. למרות שבכתיבת קוד אנחנו אוהבים לדמיין ששגיאות לא קורות אף פעם, השקעה בהודעות שגיאה טובות תמיד משתלמת.

ToCode
1 419
# למה את קוראת "טעות"? דרך אחת לשפר את הקוד שלנו היא להבין מה זה אומר לכתוב קוד גרוע, מה זה אומר "טעות", או מהו קוד גרוע בשבילכם- 1. האם קוד גרוע הוא קוד שלא עובד? או שגם קוד עובד יכול להיות גרוע? 2. האם קוד גרוע קשור לביצועים פחות טובים, או שיכול להיות שקוד יהיה גם מהיר וגם לא טוב? 3. האם קוד גרוע קשור למה שמסביבו? האם קוד יכול להיות טוב אם אין לו בדיקות? אם אין לו תיעוד? 4. האם קוד גרוע בכלל קשור ליכולת להרחיב אותו? אולי קוד גרוע זה קוד שקשה להוסיף אליו פיצ'רים מסוימים? יש פיצ'רים שבשבילם תצטרכו ממש לשכתב חלקים גדולים מהקוד? 5. האם קוד גרוע קשור ל Best Practices ולסגנון כתיבה? קוד גרוע הוא קוד שלא כתוב לפי סטנדרטים מסוימים? איזה סטנדרטים אלה? איזה סוג קוד קשה לכם לקרוא? 6. ואולי אנחנו קוראים "גרוע" לקוד שלא מטפל במקרי קצה? קוד שלא יודע להתמודד עם רשת איטית או קבצים שלא נמצאים איפה שהם צריכים להיות? קוד שמכיל בעיות אבטחה? לכולנו יש אינטואיציה לגבי "קוד טוב", קוד טוב זה קוד שאני יכול להוסיף לו פיצ'רים בקלות בלי לשבור דברים אחרים, שעובד מהר, שקל לי לקרוא אותו ולתקן בו באגים. אבל זה הכל דיבורים באוויר. בשביל להתקדם לכיוון קוד טוב עדיף להיות ספציפיים. בפעם הבאה שאתם צריכים לבנות פיצ'ר שאלו את עצמכם איזה דברים בקוד יהפכו אותו לממש גרוע (בעיניכם ולמערכת שלכם)? איזה סוגי בעיות אתם ממש לא מוכנים לראות בקוד? אם לדוגמה אני כותב אפליקציית מובייל ועבורי ועבור הלקוחות שלי אני יודע שהדבר הכי גרוע שיכול לקרות זה שהאפליקציה תתרסק באמצע, אז אני מוכן "לשלם" בקוד כפול או קצת יותר ארוך ובלבד שאני מקפיד לטפל בכל מקרי הקצה ואפילו מוודא כמה פעמים שדברים הם כמו שאני חושב שהם. לעומת זאת אם אני בפיתוח סטארט-אפ ועדיין לא בטוח מה הלקוחות שלי רוצים, אני אולי מוכן "לשלם" בהתרסקויות מדי פעם בשביל שיהיה לי קל להוסיף ולהוריד פיצ'רים ולעדכן את הממשק מהיום למחר. אי אפשר לקבל הכל, ואפילו במערכות גדולות יש בעיות. אבל כשאנחנו יודעים להגדיר בצורה ברורה את המטרות שלנו אפשר להגשים אותן. לפחות בקוד.

ToCode
1 419
# שתי דרכים טובות ואחת לא ממש כדי להדפיס אורך שורה ביוניקס יש לכם תוכנית שמייצרת פלט ואתם צריכים לגלות מה האורך של כל שורה בפלט ולהדפיס את האורך לפני השורה עצמה. איך עושים את זה? הנה שלושה רעיונות: ## הפשוט: awk הכלי הראשון שקופץ לראש הוא awk, שלמרות שהוא יודע ואוהב לחתוך את השורה למילים, אפשר להשתמש בו גם בשביל לבצע פעולות על השורה המלאה. הפקודה תהיה:
ls -l | awk '{print length($0) " " $0 }'
## הקריפטי: perl אחרי שכתבתי את זה ב awk ניסיתי למצוא עוד כלים שתומכים בתחביר דומה. מאוד רציתי את sed אבל לא מצאתי איך לחשב שם את אורך השורה. פרל היתה הבחירה הבאה:
ls -l | perl -nl -e 'print(length($_) . " $_")'
זה קצת יותר ארוך מה awk אבל עובד ומאפשר יותר גמישות אם בעתיד נצטרך. ואלה מכם שמחפשים כלי יותר מודרני יכולים תמיד להחליף את ה perl ב ruby:
ls -l | ruby -nl -e 'puts "#{$_.length} #{$_}"'
## המסובך עם הטעות: xargs הכיוון האחרון והמסובך ביותר היה להשתמש ב xargs כדי לשבור את הפלט לשורות, ואז להפעיל wc על כל שורה. זה נראה ככה:
ls -l | xargs -I % sh -c 'echo $(echo -n "%" | wc -c) "%"'
כאן צריך לשים לב למרכאות סביב כל סימני האחוז כדי ש bash לא בטעות ימחק לנו רווחים כפולים, ואפילו אחרי כל המרכאות עדיין יש כאן טעות, למשל אם בקלט יש שורה שמכילה סימן דולר אז bash ינסה לפענח את המילה שמתחילה בדולר בתור משתנה. הלקח מכל הסיפור? כשיש דרך פשוטה לפתור בעיה עדיף לבחור בה. סיכוי טוב שהיא גם תהיה יותר נכונה.

ToCode
1 419
# התיקון הלא נכון (או: למה הוובינר הופיע בשעה הלא נכונה) רבים מכם שמו לב שהוובינר באתר הופיע בשני מקומות שונים בשתי שעות שונות: בדף רשימת הוובינרים הוא הופיע בשעה הנכונה (עשר בבוקר), אבל בדף האירוע עצמו הוא הופיע בשעה שמונה בבוקר. בואו נראה למה זה קרה ומה אפשר ללמוד מזה. ## למה זה קרה: אזורי זמן כשאנחנו שומרים שעה בבסיס נתונים נוח מאוד לשמור אותה "מנוטרלת" מאזור זמן. לדוגמה עבור וובינר לא מוזר לדמיין שמישהו מארץ אחרת יצפה באתר וירצה לראות את השעות באזור הזמן שלו. בשליפה מבסיס הנתונים אנחנו רוצים להמיר את השעה לאזור הזמן לפיו רוצים להציג, וזה יכול להיות משהו ששמור בהעדפות המשתמש או בחירת אזור זמן גלובאלי לאתר כך שהשעה שתוצג תהיה זהה לכל המשתמשים. בדף רשימת הוובינרים הופיע הקוד הבא כדי להציג את השעה המתאימה של הוובינר באזור הזמן שלנו:
<% @workshops.each do |wi| %>
  <% ldate = wi.date.in_time_zone("Jerusalem") %>

  ...
  
  <%= ldate.strftime("%b") %>
המשתנה ldate הוא קיצור של המילה local_date ומחזיק את התאריך והשעה בצירוף אזור זמן. ## אז מה הבעיה? הקוד שלמעלה הוא כבר Code Smell וקל לראות את זה: הוא פותר בעיה גלובאלית בצורה נקודתית. במקום להגדיר במקום אחד שכל השעות באתר יותאמו לאזור זמן ירושלים, הקוד מטפל רק בשעה בדף רשימת הוובינרים. ככל הנראה הקוד הגיע לשם אחרי שמישהו דיווח שבדף רשימת הוובינרים השעה לא נכונה, מתוך רצון לפתור את הבעיה בלי לשבור דברים אחרים. לא צריך להתאמץ בדמיון כדי להבין מה קרה הלאה. בדף הוובינר היה לי את הקוד הבא:
<% wi = @workshop_instance %>
<% date = wi.date.strftime('%d/%m/%Y') %>
<% start_time = wi.date.strftime('%l:%M%P') %>
<% end_time = (wi.date + (wi.workshop&.duration || 0).hours).strftime('%l:%M%P') %>
וכך שעת ההתחלה של הוובינר לא קיבלה את ההתאמה לאזור הזמן שבוצעה ברשימת הוובינרים. ## התיקון בעבודה על מערכת חשוב להבין איזה פעולות אנחנו צריכים לבצע ברמה מקומית ואיזה פעולות צריכות באמת להשפיע על כל המערכת. התאמת אזור זמן למשתמש ספציפי לפי הגדרות המשתמש אמורה להיעשות ברמה מקומית, אחרי שאנחנו יודעים מי המשתמש ומה אזור הזמן המועדף עליו (אם נחליט יום אחד לממש פיצ'ר כזה). התאמת אזור זמן כללי לכל השעות באתר צריכה להתבצע במקום אחד, כדי שכל פעם שמישהו מציג שעה היא תוצג לפי אזור הזמן המועדף. בריילס הגדרות גלובאליות נמצאות בקבצים בתיקיית config ולכן תיקון השעות בכל האתר היה בסך הכל סיפור של להוסיף את השורה:
config.time_zone = "Jerusalem"
לקובץ config/application.rb. יותר חשוב מהתיקון הספציפי הוא הלקח - בפיתרון בעיה נקודתית הכי קל לברוח מ Refactoring ולתקן את הבעיה כך שכמעט לא תשפיע על מקומות אחרים במערכת. אבל יותר חכם לעצור, לחשוב, להבין את הבעיה ולשאול את עצמנו אם הבעיה היא באמת נקודתית בעמוד מסוים או בעיה כוללת שדורשת שינוי הגדרות מערכתי או אפילו Refactoring.

ToCode
1 419
# הזמנה לוובינר: טעויות נפוצות עם ריאקט ריאקט היא רק ספריית תצוגה. לריאקט יש ביצועים מצוינים. ריאקט הרבה פחות מסובכת מ X/Y/Z ולכן קוד ריאקט תמיד יוצא יותר יעיל. האמת היא שלא משנה מהי ספריית הפיתוח שתבחרו לפרויקט שלכם, אתם צריכים להכיר אותה ואת השטויות שלה כדי לא ליפול בבורות כשעוברים מ Tutorials ודוגמאות של קורסים לקוד אמיתי. ובריאקט יש לא מעט בורות כאלה. ביום חמישי הקרוב בעשר בבוקר אני אדגים מספר בורות בעבודה עם ריאקט דרך תוכניות ריאקט שמכילות באגים מעניינים, ולכל תוכנית נראה מה מקור הבעיה ומה אפשר ללמוד על ריאקט מתוך הבנה של הבעיה. הוובינר מיועד לאנשים שמכירים וכתבו ריאקט ורוצים להכיר את הבורות שלו כדי לא ליפול בהם בקוד אמיתי. בין הנושאים שאדבר עליהם יהיו: 1. טעויות נפוצות ב Data Flow בין קומפוננטות. 2. טעויות נפוצות בנושא Immutable Data. 3. טעויות נפוצות בשימוש ב Hooks, במיוחד במערך התלויות ו useEffect. 4. איך לבחור קומפוננטות בצורה יותר יעילה. 5. זמן לשאלות שלכם. הכנתי הרבה דוגמאות אז תבואו ערניים ונתראה בחמישי, אה כמעט שכחתי - קישור להרשמה: https://www.tocode.co.il/workshops/121.

ToCode
1 419
# קוד טוב, קוד עובד אלה שני צירים שונים. בציור אפשר לדמיין מערכת צירים, ציר ה x מתאים לקוד עובד או לא עובד (וכן זו סקאלה, כי תמיד יש באגים או פשרות בביצועים או מה לא), וציר y מתאים לקוד טוב או לא טוב. בכתיבת קוד חדש כולם חולמים על הרבע הימני-עליון של המפה, חיובי בציר ה"עובד" וחיובי בציר ה"טוב", אבל ברוב המקרים אנחנו לא מבינים את הדרישות מספיק טוב ונוחתים איפשהו ליד האפס. החלק היותר מעניין בעבודה הוא עדכון קוד קיים. פה ברירת המחדל של רוב המתכנתים היא להתמקד ב"עובד", ולנסות לשנות כמה שפחות כדי לתקן את הבאג או לממש את הפיצ'ר. ריפקטור שיהפוך את הקוד ליותר טוב בדרך כלל לא בא בחשבון כי הוא מחוץ לסקופ של אותו פיצ'ר או באג. אבל אם נחזור למערכת הצירים שלנו, תהליך כזה מבטיח שלאורך זמן יהיה לנו קוד רע. אנחנו מתקדמים בציר אחד ונותנים למזל לעשות את שלו בציר השני. רוב הקוד הגרוע שאנחנו כותבים לא נכתב בשבוע הראשון של הפרויקט, אלא הוא נוצר באבולוציה כשאנחנו לוקחים אבסטרקציה לא נכונה וממשיכים לבנות עליה עוד ועוד מנגנונים, במקום לתקן את היסודות. כל בניה כזאת מורידה אותנו עוד כמה נקודות בציר ה"טוב". הפיתרון היחיד לדעתי הוא לייצר נהלי עבודה שמחייבים אותנו לשפר כל קוד שאנחנו נוגעים בו תוך כדי תנועה. דוגמאות לכאלה נהלים יהיו- 1. חובה לייצר בדיקות אוטומטיות לכל באג שמתקנים. 2. להשאיר מקום למתכנתים להוסיף משימות ריפקטורינג ל Jira כבר בספרינט הבא, ולעודד מתכנתים לייצר לעצמם כאלה משימות. 3. שימוש ב Git Hooks כדי לוודא הודעות קומיט מפורטות בגיט. 4. הכנסת שידרוגי תלויות ותשתית בתור משימה חוזרת ב Jira, פעם ב X ספרינטים. יש לכם רעיונות נוספים לנהלי עבודה שמשפרים את איכות הקוד לאורך זמן? עכשיו החזרתי את התגובות אז אפשר לשתף כאן או בטלגרם.

ToCode
1 419
return draft.map(d => d.id === newNoteId ? data.data : d )
  }));
ואם היתה שגיאה והשרת לא הצליח לשמור את הפתק? בשביל זה יש לי בלוק catch:
} catch (err) {
 patchResult.undo();
}
פקודת undo תבטל את העדכון ותחזיר את רשימת הפתקים ב Cache לערך שלה לפני הכישלון. נ.ב. הקוד לא מטפל במצב של כמה פתקים שנוצרים יחד, חלקם מצליחים וחלקם נכשלים. במצב כזה פתק שנכשל עשוי להחזיר את רשימת הפתקים יותר מדי אחורה, למצב שלא מראה את הפתקים שהצליחו, כי ה patchResult של הפתק שנכשל נוצר לפני שפתקים אחרים הצליחו. בשביל להתמודד עם מצב כזה נצטרך לעדכן את הקוד ב catch להיות יותר ספציפי וממש למחוק את הפתק החדש לפי ה id שלו באופן הבא:
} catch (err) {
 dispatch(
   notesApi.util.updateQueryData('getNotes', undefined, (draft) => {
     return draft.filter(note => note.id !== newNoteId);
   }));
  }
קוד כזה מאפשר ל undo להיות יותר ספציפי וממש למחוק את הפתק החדש שניסינו להכניס במקום להחזיר את המצב למה שהיה כשיצרנו את ה patch.

ToCode
1 419
# עדכונים אופטימיים ב Redux Toolkit Query המושג "עדכון אופטימי" מתאר עדכון שבו אנחנו בטוחים שהכל יעבוד, ולכן אנחנו מעדכנים את ממשק המשתמש במידע החדש עוד לפני שקיבלנו אישור מהשרת שאכן המידע תקין, ורק אם השרת ידווח על שגיאה אז נבטל את העדכון שעשינו. עדכון אופטימי מציע חווית משתמש יותר טובה כי משתמשים לא צריכים לחכות ששרת יאשר משהו שאנחנו יודעים שב 90% מהמקרים מצליח. ל RTK Query יש דרך מובנית ומאוד נוחה לממש עדכונים אופטימיים בזכות שילוב של מספר מנגנונים של הספריה: 1. ספריית RTK Query מחזיקה את כל התוצאות של כל השאילתות בזיכרון, ומאפשרת לנו לעדכן את השאילתות השמורות. 2. הספריה משולבת עם Immer ומאפשרת לנו "לזכור" את המצב לפני העדכון ולבטל אותו בפקודה אחת מובנית. 3. באופן אוטומטי כל קוד ה UI שמציג תוצאות של שאילתות מחובר לאותו זיכרון מטמון ומתעדכן כשאנחנו מעדכנים את תוצאות השאילתות, גם אם העדכון הוא פיקטיבי (כלומר עדכון אופטימי). ככה זה נראה בקוד:
    createNote: builder.mutation({
      query: (noteText) => ({
        url: `/notes`,
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ text: noteText }),
      }),      
      async onQueryStarted(noteText, { dispatch, queryFulfilled }) {
        let newNoteId = nanoid();

        const patchResult = dispatch(
          notesApi.util.updateQueryData('getNotes', undefined, (draft) => {
            return [...draft, { text: noteText, id: newNoteId }];
          }));
        try {
          const data = await queryFulfilled
          dispatch(
            notesApi.util.updateQueryData('getNotes', undefined, (draft) => {        
              return draft.map(d => d.id === newNoteId ? data.data : d )
            }));
       } catch (err) {
        console.log(`error`);
        patchResult.undo();
       }
      }
    })
הקוד מגדיר פעולה חדשה בשם createNote, עבור אפליקציה ששומרת רשימה של פתקים. בשביל ההגדרה אני מפעיל את builder.mutation ומעביר לו אוביקט עם שני מפתחות: 1. המפתח query יוצר את קוד בקשת ה POST שאני שולח לשרת כדי ליצור פתק חדש. לפתק חדש יש בסך הכל את הטקסט בפתק. 2. המפתח onQueryStarted מחזיק את הפונקציה שאחראית על העדכונים האופטימיים. אותה פונקציה מקבלת את הטקסט של הפתק שבגללו התחלנו את הפעולה, ואוביקט API ממנו אני משתמש במפתחות dispatch ו queryFulfilled. אני יודע שלכל פתק יש מזהה, אבל אין לי עדיין את המזהה כי השרת עוד לא יצר אותו, אבל אני אופטימי אני יודע שהשרת עוד מעט יקבל את הפתק וייצור לי עבורו מזהה, אז בינתיים אני מגריל מזהה משלי עם nanoid ומשתמש בו בתור ה id של הפתק. עכשיו אני ממשיך להיות אופטימי ומפעיל את הקוד:
const patchResult = dispatch(
  notesApi.util.updateQueryData('getNotes', undefined, (draft) => {
    return [...draft, { text: noteText, id: newNoteId }];
}));
הקוד עושה שני דברים: הוא גם מעדכן את ערך ההחזר של הנתיב getNotes, שזה הנתיב שמחזיר את רשימת כל הפתקים על השרת. העדכון לוקח את הערך הישן (מזוהה כאן בשם המשתנה draft) ומוסיף לו את הפתק החדש כמו שאני חושב שהולך להגיע מהשרת. זה מספיק בשביל לעדכן את כל ממשק המשתמש ברשימה החדשה. הדבר השני שהקוד עושה הוא להחזיר אוביקט טלאי. אוביקט הטלאי שמוחזר מ dispatch מכיל את רשימת כל השינויים בין התוצאה שהיתה שמורה במטמון לבין התוצאה החדשה שאני שמרתי במטמון, ומאפשר לחזור אחורה לתוצאה הקודמת עם פונקציית undo שלו. בנקודה הזאת מבחינת המשתמש כל הממשק מראה את המצב כאילו הפתק נוצר ונשמר כבר בשרת. המשתמש ממשיך לעבוד עם המערכת רגיל לגמרי. אבל מאחורי הקלעים אנחנו יודעים ששלחנו עדכון לשרת ושאנחנו אמורים לקבל ממנו תשובה, ולכן מחכים שתשובת השרת תגיע:
const data = await queryFulfilled
השרת עונה לי עם הפרטים של הפתק החדש שנוצר, ובנקודה הזאת אני שוב צריך לעדכן את רשימת הפתקים השמורה שלי: אולי השרת החליף חלק מהמילים בפתק, אולי (בטוח) השרת נתן לפתק מזהה ייחודי משלו, אולי הוא הוסיף שדות - בכל מקרה אני רוצה להחליף את הפתק שאני חשבתי שהשרת ייצור בפתק האמיתי שהשרת יצר, וזו מטרת פקודת העדכון בשורה הבאה:
dispatch(
  notesApi.util.updateQueryData('getNotes', undefined, (draft) => {

ToCode
1 419
# טיפ רידאקס - השתמשו ב preloadedState כדי לבדוק יותר בקלות בדוגמת ההתחלה מהירה של Redux Tolkit הם יוצרים וימייצאים את מחסן המידע באותה שורה באופן הבא:
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from '../features/counter/counterSlice'

export const store = configureStore({
  reducer: {
    counter: counterReducer,
  },
})
שינוי קטן והקוד הזה הופך להרבה יותר קל לבדיקה. במקום ליצור ולייצא את ה store אני כותב פונקציה שיוצרת store חדש, וגם יודעת לקבל preloadedState:
export function createStore(initialState) {
  return configureStore(Object.assign({}, {
    reducer: {
      counter: counterReducer,
    },
  },
  initialState ? { preloadedState: initialState } : {}));
}
וככה אני יכול להשתמש בפונקציה כדי לבדוק את ה store בכל state ראשוני שארצה:
test('inc from 10 to 11', () => {
  const store = createStore({ counter: { value: 10 }});
  store.dispatch(increment());
  expect(store.getState().counter).toEqual({ value: 11 });
});
יותר מזה, אפשר להשתמש ב createStore כמה פעמים, למשל בהפעלה אחת לא להעביר פרמטרים וככה לקבל את ה State הראשוני המלא, להוציא אותו מה store, לשנות רק את הערכים שצריכים לבדיקה ואז להפעיל createStore שוב עם הסטייט שיצרתם.