במאמר הקודם דיברתי על Travis CI ועל איך גורמים לו להריץ את הבדיקות האוטומטיות בכל פעם שמישהו עושה לי pull request (או שאני עושה לעצמי, תמיד תמנעו מלעשות push ישירות ל-master). לקראת הסוף כתבתי שבדיקות אוטומטיות הן רק חלק מתהליך ה-CI. חלק חשוב לא פחות מתהליך ה-CI הוא בדיקת הקוד הסטטית או בשפת גויי הים: Static code analysis. מה זה אומר? לכל קוד יש סטנדרטים משלו בהתאם לסדר הטוב ולשגיונות של המתכנת שאחראי על הקוד. למשל, בקוד של production, אני לא רוצה שיהיה console בכלל. או אני רוצה לאכוף את strict mode של ג'אווהסקריפט בכל הקבצים שלי או אפילו דברים ממש קטנוניים כמו למשל רווח בין סוגריים מסולסלות ונקודה אחרי כל הערה. כל ערימת הכללים הזו נקראת סטנדרטים של קוד.
הסטנדרטים האלו מאוד חשובים לאיכות הקוד כיוון שכך הקוד הופך להיות מאוד קריא ונראה כאילו אדם אחד כותב אותו אפילו אם מספר אנשים השתתפו בכתיבתו על פני זמן. קוד קריא הוא קריטי במיוחד בפרויקטים גדולים (אבל לא רק) וכמובן אכיפה של סטנדרטים טובה גם לביצועים ולהרצה. אם אנו אוכפים למשל 'תנאי יודה' (כלומר false === x ולא x === false) אז אנחנו ממזערים את טעויות ההשמה. אם אנחנו אוכפים אי שימוש במשתנים עם אותו שם, אנחנו מונעים בילבול.
השאלה הגדולה היא איך אוכפים את זה? באופן תיאורטי, אנחנו יכולים להעביר אינספור הדרכות למפתחים ולסקור את הקוד שלנו ב-7 עיניים על מנת לוודא שכל הכללים נאכפים. אבל זו עבודה של ימי הביניים. כאשר אני בודק קוד של מתכנת אחר, אני אמור לבדוק איך הקוד עובר ולא אם הוא שם רווח אחד בדיוק בין שם הפונקציה לסוגריים ואם יש שורה אחת בין הגדרת פונקציה לאחרת או שתי שורות. זה בזבוז זמן, לא יעיל ומועד לטעויות. בדיוק בשביל זה יש כלי ניתוח קוד שיכולים לרוץ באופן אוטומטי ולהודיע על שגיאות בסטנדרטים. אנחנו מגדירים בקובץ אחד את הכללים והתוכנה תבדוק ותתריע.
הכלי האהוב עלי הוא eslint שמיועד לבדיקת קוד של JavaScript (על המקבילה ב-PHP כתבתי במאמר אחר). על מנת להשתמש בו, אני צריך ליצור קובץ בשם eslintrc. (שימו לב לנקודה בתחילת השם) ולהכניס אותו לתיקיה הראשית של הפרויקט שלי. בקובץ הזה יהיה JSON (אפשר גם פורמטים אחרים) שבו כל הכללים שאני מעוניין בהם יפורטו. למשל:
{ "env": { "node": true, "es6": true }, "rules": { "comma-dangle": [ 2, "never" ], "no-control-regex": 2, "no-debugger": 2, "no-dupe-args": 2, "no-dupe-keys": 2, "no-duplicate-case": 2, "no-empty-character-class": 2, "no-ex-assign": 2, "no-extra-parens": [ 2, "functions" ], "no-extra-semi": 2, "no-func-assign": 2, "no-invalid-regexp": 2, "no-irregular-whitespace": 2, "no-obj-calls": 2, "no-proto": 2, "no-template-curly-in-string": 2, "no-unexpected-multiline": 2, "no-unreachable": 2, "no-unsafe-negation": 2, "use-isnan": 2, "valid-typeof": 2, "no-fallthrough": 2, "no-global-assign": 2, "no-multi-spaces": 2, "no-octal": 2, "no-redeclare": 2, "no-self-assign": 2, "no-unused-labels": 2, "strict": [ 2, "global" ], "no-delete-var": 2, "no-undef": 2, "no-unused-vars": [ 2, { "args": "none" } ], "no-mixed-requires": 2, "no-new-require": 2, "no-path-concat": 2, "no-restricted-modules": [ 2, "sys", "_linklist" ], "brace-style": [ 2, "1tbs", { "allowSingleLine": true } ], "comma-spacing": 2, "eol-last": 2, "func-call-spacing": 2, "indent": [ 2, 2, { "SwitchCase": 1, "MemberExpression": 1 } ], "key-spacing": [ 2, { "mode": "minimum" } ], "keyword-spacing": 2, "linebreak-style": [ 2, "unix" ], "new-parens": 2, "no-mixed-spaces-and-tabs": 2, "no-multiple-empty-lines": [ 2, { "max": 2 } ], "no-trailing-spaces": 2, "quotes": [ 2, "single", "avoid-escape" ], "semi": 2, "space-before-blocks": [ 2, "always" ], "space-before-function-paren": [ 2, "never" ], "space-in-parens": [ 2, "never" ], "space-infix-ops": 2, "space-unary-ops": 2, "arrow-parens": [ 2, "always" ], "arrow-spacing": [ 2, { "before": true, "after": true } ], "constructor-super": 2, "no-class-assign": 2, "no-confusing-arrow": 2, "no-const-assign": 2, "no-dupe-class-members": 2, "no-new-symbol": 2, "no-this-before-super": 2, "prefer-const": 2, "rest-spread-spacing": 2, "template-curly-spacing": 2, } }
זה קוד אמיתי מאחד הפרויקטים שלי, אפשר לנסות ולנחש מה כל כלל. למשל בהתחלה אפשר לראות שמדובר בקובץ שמניח שזה node ומניח שאני משתמש ב-ECMAScript 6. ניתן לראות שאני לא מאשר ארגומנטים כפולים, מפתחות כפולים או סוגריים כפולים. רשימת כל הכללים של eslint נמצאת בדוקומנטציה שלהם ויש לא מעט פרויקטים בגיטהאב ובמקומות אחרים שאפשר לקחת מהם את הרשימות. לכל מתכנת ולכל פרויקט יש את הרשימה שלו שעל יתרונותיה הוא מוכן להתווכח.
אחרי שהכנתי את הeslintrc. אני צריך להריץ אותו. על מנת להריץ אותו, אני צריך מודול של node.js שנקרא… eslint. ההתקנה שלו היא פשוטה למדי:
npm install eslint -g
ה g- הוא על מנת להתקין את ה-eslint באופן גלובלי על מנת שהוא יהיה זמין משורת הפקודה בקונסולה. מומלץ ואף רצוי להתקין אותו מקומית:
npm install eslint --save-dev
ה save-dev– הוא על מנת שהמודול יכנס ל package.json.
אחרי שעשינו את ההתקנות, אני יכול לכתוב eslint בקונסולה/cmd ולראות משהו כזה:
עכשיו אני יכול להריץ את ה-eslint באופן הבא דרך הקונסולה או ה-cmd:
eslint -c .eslintrc lib/**/*.js index.js
מה הולך פה? -c זה הנתיב של קובץ הקונפיגורציה שלנו. החלקים האחרים הם הקבצים שאותם אני רוצה לבדוק. במקרה הזה כל קבצי ה-JS שנמצאים ב-lib (ובתתי התיקיות, אם יש) ואת index.js. הנתיבים הם גמישים ואפשר להכניס כמה שרוצים.
כתיבה של השורה הזו תבצע בדיקה של כל הקבצים ותציג לי הערות שונות לתיקון.
שימו לב שבדוגמה אני מריץ את זה על חלונות. אבל אפשר להריץ את זה מן הסתם גם בלינוקס/מק.
אם תוסיפו fix– להרצה, eslint יפתור את כל השגיאות שאפשר לפתור באופן אוטומטי כמו רווחים, אינדנטציה וכו'.
אני עכשיו יכול לתקן את כל הטעויות. אם לא מופיעות לי טעויות, סימן שהכל תקין. כיוון שאני לא רוצה להריץ את השורה הזו, עם ציון הנתיבים, בכל פעם שאני רוצה לבדוק קוד, אני יכול להכניס את זה ל-package.json. ממש ככה:
"scripts": { "test": "mocha test", "eslint": "eslint -c .eslintrc lib/**/*.js index.js" }
לצד ה-mocha test, שעליו כתבתי במאמר הקודם, יש לי עכשיו משימה שנקראת eslint. אותה אני יכול להריץ באמצעות npm run eslint.
טוב ויפה, כך אנחנו משתמשים ב-eslint על מנת לוודא שקוד ה-JavaScript שנכתב בנוי היטב ולפי הכללים. אבל איך בדיוק אני יכול לבדוק את הקוד כאשר מישהו (או אני) מבצע pull request? בדיוק כמו בבדיקות אוטומטיות, גם כאן אני מבצע את זה באמצעות Travis CI, הכלי הנהדר ל-continuous integration שמסייע לי למזער את הכאב ואי הוודאות הכרוכים במיזוג קוד חדש לתוך הקוד הישן. במאמר הקוד הראיתי איך אני משתמש ב-Travis CI להרצת בדיקות אוטומטיות על כל הקוד בכל Pull Request. אבל אפשר להשתמש בו להריץ גם Static code analysis. כל מה שצריך לעשות, אחרי כמובן הגדרת ה-eslintrc. והמשימה ב-package.json זה לשנות את קובץ ההגדרות של Travis: הלא הוא travis.yml. (שימו לב לנקודה בסוף). בו אנחנו צריכים לפרט לו את כל המשימות – התקנת eslint באופן גלובלי (ואחרי כן התקנת כל המודולים שנדרשים על ידי המודול שלנו ומפורטים ב-package.json) והרצה של סקריפט הבדיקות והסקריפט שבודק את כללי הקוד. איך? הנה קובץ ה-yml לדוגמה:
language: node_js node_js: - "4" - "5" - "6" - "node" install: - npm install - npm install eslint -g script: - npm run eslint - npm test
לא צריך להיות מומחה IT גדול כדי לראות מה עשיתי כאן. אני מפרט את הסביבות של node שאני רוצה לבדוק מולן. בחלק ה-install אני מפרט מה אני רוצה לבצע לפני ההרצה (התקנה גלובלית של eslint והתקנה של package.json) ובחלק של הסקריפט אני מפרט מה צריך לרות: במקרה הזה npm run eslint ו npm test. כשלון של בדיקה או כשלון בדיקת הסטנדרטים יכשיל את הבדיקה ואקבל על כך התראה ב-pull request.