בטיחות נאל בסי שארפ (null safety)


בטיחות נאל

הקדמה

בסיס

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

שגיאות נאל

שגיאות null בדרך כלל מסתכמות בבניסיון לגשת או לבצע פעולה על אובייקט כלשהו, שבכלל לא קיים (כלומר, הוא null). ולכן, בטיחות הנאל היא בגדול ווידוא שאותו האובייקט באמת קיים, לפני הגישה אליו. שפות מ"הדור החדש" כגון קוטלין ודארט בנויות בצורה המחייבת בטיחות נאל, ובהן כתיבה של קוד הניגש לערך שיכול להיות null ללא בדיקה שהוא אינו כזה, תיתן שגיאה שלא תתן להריץ את הקוד כלל. בסי שארפ קיימים כל הכלים לבניית קוד בטוח נאלית, אך בניגוד לשפות מודרניות היא לא כופה בטיחות נאל, ואם נרשום קוד כזה יופיעו לנו אזהרות בלבד. נתייחס לאזהרות אלו כחובת יישום במהלך מדריך זה, למרות שבעקרון ניתן להתעלם מהם.
כברירת מחדל אזהרות בטיחות הנאל והכלים לבנייתה מאופשרים בפרוייטקים בסי שארפ, במידה ואינם, נדרש לשנות בקובץ xml הפתרון את הערך Nullable לenable. או שנרשום מעל הקוד הבטוח נאלית את התג, #nullable enable.

כתיבת הקוד

משתנים

על מנת להגדיר משתנה הלא יכול לקבל ערכי null, נגדיר משתנה כמו שידענו להגדיר עד עכשיו, לדוגמא משתנה מהסוג string,

#nullable enable
string MyName = "Tomer Ashtamker";
string? MyNullName = null;

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

עבודה עם משתנים מאופשרי נאל

דוגמא א'

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

#nullable enable
class Person{
____public string FirstName {get;set;} = "None";
____public string? LastName {get;set;}
____public Phone? PersonalPhone {get;set;}
____public Person (string FirstName,string? LastName, Phone? PersonalPhone)
____{
________this.FirstName = FirstName;
________this.LastName = LastName;
________this.PersonalPhone = PersonalPhone;
____}
}
class Phone
{
____public string? Number {get;set;}
____public Phone(string? Number)
____{
________this.Number = Number;
____}
}

Person?[]? Persons = new Person[3];
Persons[0] = new Person("Tomer","Ashtamker",new Phone(null));
Persons[1] = new Person("Ana", null, new Phone("0525381648"));
Persons[2] = new Person("Kanye","East",null);
for(int i=0;i<3;i++)
____Console.WriteLine(Persons?[i]?.PersonalPhone?.Number ?? "No Phone Assigned");

מה בעצם יש פה? אז ראשית הגדרתי שני קלאסים:

  • איש - מכיל את המאפיין FirstName הלא יכול להיות נאל, מאותחל כ"None". בנוסף המאפיינים LastName,PersonalPhone היכולים להיות נאל, הראשון הוא string והשני עצם מסוג טלפון.
  • טלפון - מכיל את המאפיין Number שהינו סטרינג האמור להכיל את מספר הטלפון, ויכול להיות null.

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

  • ?[] - גישה למקום כלשהו במערך. במידה והמערך שווה לnull, פעולת הגישה, והפעולות שאחריה לא יתקיימו (וכך לא תיווצר שגיאה), ויוחזר הערך null.
  • ?. - גישה למאפיין פנימי של אובייקט. במידה והאובייקט הוא null, פעולת הגישה, והפעולות שאחריה לא יתקיימו, ויוחזר הערך null.
  • ?? - במידה והאובייקט מצד שמאל לסימן הוא null יוחזר האובייקט מצד ימין. במידה ולא השמאלי יוחזר.

בעצם בעזרת הפעולות האלו בניתי בשורת קוד אחת את הפעולה שתכננתי (השורה האחרונה) הפועלת כך,

  1. ניגש למקום כלשהו במערך Persons, במידה והמערך הוא נאל, יוחזר null (עם ?[]).
  2. ניגש לעצם Person שנלקח מהמערך Persons, במידה והעצם הוא נאל, יוחזר null (עם ?.).
  3. ניגש למאפיין PersonalPhone של העצם Person, במידה והמאפיין הוא נאל, הערך נאל יוחזר (עם ?.).
  4. ניגש למאפיין Number של העצם Phone.
  5. במידה ובכל אחד מהצעדים הקודמים הוחזר null, יוחזר הסטרינג "No Phone Assigned" במקום (עם ??).

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

"No Phone Assigned"
"0525381648"
"No Phone Assigned"

דוגמא ב'

עוד שתי פעולות שנוכל להשתמש בהם הם,

  • ??= - במידה והאובייקט מצד שמאל הוא null, יוזן אליו הערך מצד ימין. במידה ולא לא יקרה כלום.
  • !. - גישה למשתנה העלול להיות null בעזרת עקיפת אזהרת בטיחות הנאל. נשתמש מתי שנדע מראש שהמשתנה שמאפשר אחסון ערך נאל בו, לא מאחסן כזה. במידה ומאחסן נקבל שגיאת נאל.
  • ![] - כמו האחרון, רק לגישה למקום כלשהו במערך.

לדוגמא,

#nullable enable
void FillPersonsArray (Person?[]? PersonsArr)
{
____for(int i=0;i<(PersonsArr?.Length ?? 0);i++)
____{
________PersonsArr![i] ??= new Person("Israel","Israeli",new Phone("1234567890"));
____}
}
Person?[]? Persons = new Person[5];
FillPersonsArray(Persons);

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

  1. ניצור לולאת פור, שתמשיך עד שתעבור על כל ערכי המערך. במידה והמערך הוא בכלל נאל, הלולאה לא תרוץ כי בעזרת הפעולה ?? יוגדר סיום הלולאה כש i<0 מה שלא יתקיים גם בהרצה הראשונה.
  2. ניגש לאלמנט במקום i, בעזרת הפעולה ![] מכיוון שעקב תנאי הלולאה נדע שהמערך אינו null, שכן נכנסנו ללולאה. ולכן שימוש בפעולה יהיה בטוח. לאחר מכן נשתמש בפעולה ??= על מנת להזין למערך את עצם האיש הגנרי במידה והמיקום הנוכחי מכיל ערך נאל.

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




אין תגובות:

הוסף רשומת תגובה