December 26, 2025

C++ Coding Standards - Joint Strike Fighter

הקדמה

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

  • השפה נבנתה ב1979.
  • השפה מאפשרת תכנות פרוצדורלי ותכנות בעזרת מחלקות.
  • השפה מכונה “שפת ביניים” בגלל יכולות הגישה לזיכרון שלה.
  • לשפה יש תכונות נוספות עיליות כגון זיהוי קריאות, גנריות וספרייה סטנדרטית חזקה.
  • גרסאות בשפה יוצאות כל 3 שנים, הסטנדרט קורא לזה “שיטת הרכבת”.
  • הגרסא האחרונה היא C++ 23.
  • הגרסא הבאה היא C++ 26.

שפת C++ היא שפה עוצמתית עם ביצועים גבוהים.
פיתחתי בה במשך שנים, והיא טובה כמעט לכל דבר.
עד כדי כך טובה שבחרו בה כדי לפתח במטוסים!

אך לא כולם מרוצים ממנה - ארגוני ביון ואפילו הבית הלבן מעדיף לא להשתמש בה:

הבית הלבן ממליץ שלא להשתמש בשפה

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

אז למה שייבחרו בה ל…מטוסים???

מוזמנים לענות לנו בדיסקורד שלנו - לחצו כאן

C++ JSF - Joint Strike Fighter

מפתחי תוכנה ממש רצו להשתמש ב-C++ למרות כל המורכבויות בה.
כדי לדאוג שהשימוש בה יהיה יעיל ובטוח - הם ישבו וחשבו,

  • איך לנהל את הפיתוח נכון.
  • איך לדאוג שכל קוד יהיה תקין.
  • איך לדאוג לשרשראות של תלויות.

בשביל זה נוצר סטנדרט חדש הנקרא - JSF AV Rules.
זהו סטנדרט ששימש את תעשיית החלל והאויר.

ניתן לקרוא את המסמך כאן:

Stroustrup JSF AV Rules

מה יש במסמך?

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

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

  • Reliability
  • Portability
  • Maintainability
  • Testability
  • Reusability
  • Extensibility
  • Readability

העקרון השני שהם מדברים עליו זה - Coupling & Cohesion.

כתבתי על העקרון הזה כאן:

דברים שהייתי רוצה לדעת כשהתחלתי לתכנת

והעקרון השלישי כבר מתחיל להגדיר חוקים לגבי גודל הקוד.

  • Logical lines of code
  • No self modifying code
  • Cyclomatic complexity below

פילוסופיית הסטנדרט

המסמך מגדיר סט חוקים אך עצם הגדרת הביטויים: Should Will Shall מגדיר היררכיה ברורה.
כדי לשבור חוק Should צריך אישור מהראש צוות.
וכדי לשבור את החוקים Will או Shall צריך אישורים נוספים.

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

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

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

החוקים

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

למשל הסטנדרט מגדיר אילו תווים ניתן להשתמש בהם, אילו ספריות מאושרות לשימוש, איך להשתמש בהדרים ועוד…

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

כמה נקודות מעניינות מהסטנדרט

תשומת לב ל-signed & unsigned

מפתחים שלמדו אסמבלי, שפת C למדו לעומק יותר את משמעות ה-Signed & Unsigned.

חוקים בדוגמא הזו:

164.1 - Right Shift operator

The left-hand operand of a right-shift operator shall not have a negative value

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

אז הסיבה אולי כתובה כ-“האימפלמנטציה בוחרת” אבל כאן מסתרר משהו גודל יותר.

אנחנו לא יכולים לסמוך על קוד שלא נדע מה הוא עושה בסביבות שונות.

שימוש בטוח בפויינטרים וטייפים

Rule 97 - Arrays

Arrays shall not be used in interfaces. Instead, the Array class should be used

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

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

  • להעביר עם Null terminated strings
  • להעביר עם גודל.

בנוסף לקידודים שונים, זה יכול לבלבל בין UTF8 ו-UTF16.
הסטנדרט הגביל את השימוש בקידוד מלכתחילה אבל אם מצאתם סיבה להשתמש במערך הסטנדרט יגיד לכם - תשתמשו ב-Array<char> ולא char*.

Rule 175 - Do not use NULL

A pointer shall not be compared to NULL or be assigned NULL; use plain 0 instead.

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

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

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

Rule 177 - User defined type conversion

User-defined conversion functions should be avoided.

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

Flow control ברור

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

Rule 189 - Gotos

The goto statement shall not be used.

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

Rule 191 & 192 - Continue and Break

The continue statement shall not be used.

The break statement shall not be used (except to terminate the cases of a switch statement)

זו גישה מעניינת, על מנת לצמצם את התנאים והמורכבות של פיצול בתוך לולאות כותבי הסטנדרט פשוט אמרו לכולם - Don't.

גם אני כתבתי קוד מלא ב-breakים בתוך לולאות במיוחד כאשר יש 2 לולאות:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <vector>
#include <utility>
#include <iostream>

int main() {
std::vector<std::vector<int>> grid = {
{ 1, 2, 3},
{ 4, 42, 6},
{ 7, 8, 9}
};

const int target = 42;

bool found = false;
std::pair<int,int> pos{-1, -1};

for (int i = 0; i < (int)grid.size(); ++i) {
for (int j = 0; j < (int)grid[i].size(); ++j) {
if (grid[i][j] == target) {
found = true;
pos = {i, j};
break;
}
}

if (found) {
break;
}
}

if (found) {
std::cout << "Found at (" << pos.first << "," << pos.second << ")\n";
} else {
std::cout << "Not found\n";
}
}

בשביל לצמצם את זה נוכל לבצע את הדבר הבא:

1
2
3
4
5
6
7
8
9
10
11
12
13
std::optional<std::pair<int,int>> find_value(
const std::vector<std::vector<int>>& grid,
int target
) {
for (int i = 0; i < (int)grid.size(); ++i) {
for (int j = 0; j < (int)grid[i].size(); ++j) {
if (grid[i][j] == target) {
return std::pair{i, j};
}
}
}
return std::nullopt;
}

Rule 208 - C++ exceptions shall not be used

C++ exceptions shall not be used

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

Rationale: Tool support is not adequate at this time.

זה מראה גם על הידיעה הפשוטה שבזמן שכתבו את המסמך הזה לפני יותר מ-20 שנה, הכלים היו מועטים.
רק ב-1998 ייצא הסטנדרט הרשמי ל-c++98 ועד ל-2011 כאשר יצא c++11 היו חוסרים רבים לשפה.

היום מי שמפתח בשפה יודע ש-C++17, C++20 והגרסאות החדשות הן הרבה הרבה יותר מתקדמות מבעבר.

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


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

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

הסטנדרטים היום

C++ Core guidelines

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

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

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

https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines

MISRA C++:2023

הכלים והשפה נהיו מקצועיים יותר וטובים יותר, השגיאות נהיו קריאות יותר והקוד נהיה “קל” יותר לפיתוח.
ובכל זאת השפה עדיין נחשבת ל”מסוכנת” - באיזה קטע מסוכנת?
שקוד שלא נבדק כראוי עדיין יכול לגרום לשגיאות בזיכרון.
לכן MISRA C++:2023 עדכנו את הסטנדרט שלהם מה-JSF והציעו אלטרנטיבה מודרנית.

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

זהו לא סטנדרט “מחייב” ז”א אין שום סיבה לחברה להשתמש בה סתם ככה.
אולם זה עוזר לחברות על מנת להשלים סטנדרטים אחרים שכן מחייבים כגון ISO 2626 ו-IEC 61508.

למען סקרנותכם מוזמנים לעיין בסטנדרטים לבד.

ניתן להיעזר באתר הזה כדי לראות את החוקים:
https://www.mathworks.com/help/bugfinder/misra-cpp-2023-rules-and-directives.html?s_tid=CRUX_lftnav

כדי לקנות את העותק הרשמי:
https://misra.org.uk/product/misra-cpp2023/


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

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

תודה על הקריאה!

על הפוסט

הפוסט נכתב על ידי Ilya, רישיון על ידי CC BY-NC-ND 4.0.

שתפו את הפוסט

Email Facebook Linkedin Print

קנו לי קפה

#Software#Docs#Standards