אחרי שחיפשתם זמן רב מודולים ואפילו השתמשתם באחד או שניים מהם, הבנתם ודאי את החשיבות הרבה של תיעוד טוב.
בין אם תכתבו קוד כחלק מפרויקט צוותי או שתשחררו מודול בקוד פתוח, איכות התיעוד תקבע אם יהיו לכם לקוחות מרוצים או אם תצטרכו לעמוד מול המון זועם.
כמפתחים וכמשתמשים, התיעוד עוזר לנו להבין מהי תכליתם של מודולים, טיפוסים, מחלקות, פעולות ופונקציות.
הוא עוזר לנו להתמצא בקוד ולהבין במהירות אם מה שאנחנו רואים מתאים לנו ומה תהיה צורת השימוש המיטבית בו,
ובכלל, בשפה כמו פייתון, שמקדשת את ערך קְרִיאוּת הקוד, יש ערך גדול מאוד לתיעוד ראוי.
במחברת הקרובה נלמד על תיעוד ראוי בפייתון, על מוסכמות תיעוד ועל גישות שונות לתיעוד.
לפני שנדון לעומק בתיעוד, נדבר מעט על ההבדל שבין הערות בקוד לבין תיעוד בקוד.
הערות הן שורות מלל המיועדות למפתחים.
הן מכילות מידע אינפורמטיבי על החלטות שקיבלתם בנוגע לקוד. מידע זה נועד לעזור לעמיתיכם לתחזק את הקוד.
הן יופיעו קרוב ככל האפשר שאפשר לקוד שאליו הן מתייחסות, לעיתים אפילו ממש בשורה של הקוד עצמו.
הערות אמורות להסביר למה דבר מסוים כתוב כמו שהוא כתוב, ולעולם לא מה כתוב בקוד.
לעומתן, תיעוד הוא מלל המיועד לאנשים שמשתמשים בקוד שלכם.
הוא יוצמד למודולים, למחלקות, לפעולות ולפונקציות, ויספר בקצרה ובענייניות מה עושה הפונקציה ואיך להשתמש בה.
אם, לדוגמה, פיתחתם מודול ושיחררתם אותו לאינטרנט,
המשתמשים בו יצפו לתיעוד שברור ממנו איך משתמשים בקוד, ומה קורה בכל אחד ממקרי הקצה.
כפי שאתם ודאי זוכרים מהמחברות שקראתם עד כה, הערה תתחיל בתו #
ורווח שיבוא אחריו.
אחרי הסולמית והרווח יבוא המלל שמסביר את ההחלטות שהתקבלו לצורך כתיבת הקוד.
נראה לדוגמה קוד קצר לפירוק מספרים ראשוניים עם הערות שהוטמעו בו:
In [ ]:
import math
def factorize_prime(number):
while number % 2 == 0:
yield 2
number = number // 2
# `number` must be odd at this point (we've just factored 2 out).
# Skip even numbers. Square root is good upper limit, check
# https://math.stackexchange.com/a/1039525 for more info.
divisor = 3
max_divisor = math.ceil(number ** 0.5)
while number != 1 and divisor <= max_divisor:
if number % divisor == 0:
yield divisor
number = number // divisor
else:
divisor += 2
# If `number` is a prime, just print `number`.
# 1 is not a prime, 2 already taken care of.
if number > 2:
yield number
print(list(factorize_prime(5)))
print(list(factorize_prime(100)))
בקוד שלמעלה מופיעים שני מקרים של "Block Comment".
מדובר בשורה אחת או יותר של הערה שבאה לפני פיסת קוד, ומטרתה לבאר דברים בקוד.
ה־Block ייכתב באותה רמת הזחה של הקוד שאליו הוא מתייחס, וכל שורה בו תתחיל בתו #
שלאחריו יבוא רווח.
נשים לב לנקודות סגנון חשובות, שניכרות היטב בדוגמה האחרונה:
הערה יכולה להיות ממוקמת גם בסוף שורת הקוד:
In [ ]:
print("Hello World") # This is a comment
במקרה שבו ההערה נמצאת באותה שורה יחד עם הקוד (כמו בתא האחרון), נהוג לשים לפחות שני רווחים לפני הסימן #
.
זוהי צורה מקובלת פחות לכתיבת הערות, כיוון שהיא מאריכה את שורת הקוד, שוברת את רצף הקריאה ולרוב מיותרת.
מתכנתים מתחילים נוטים להסביר מה הקוד עושה, ולשם כך הם משתמשים לעיתים קרובות ב־Inline Comments.
הימנעו מלהסביר מה הקוד שלכם עושה.
דוגמה להערה לא טובה:
In [ ]:
snake_y = snake_y % 10 # Take the remainder from 10
ולעומתה, הערה המתקבלת על הדעת:
In [ ]:
snake_y = snake_y % 10 # Wrap from the bottom if the snake hits the top
בעולם התוכנה המקצועי ניטש ויכוח רב שנים על מתי נכון להוסיף הערות לקוד.
דעה פופולרית אחת דוגלת בריבוי הערות בקוד.
אלו המצדדים בדעה זו מוסיפים לקוד שלהם הערות לצרכים מגוונים:
# FIXME
לציון קטע קוד שצריך לתקן.# TODO
ואחריו מלל שמסביר משהו שעדיין צריך לבצע ועוד לא נפתר.# HACK
לציון מעקף שנועד לפתור בעיה, פעמים רבות בדרך בעייתית.
דעה פופולרית אחרת דוגלת בצמצום ההערות בקוד למינימום ההכרחי.
אלו המצדדים בדעה זו משתדלים להמעיט ככל האפשר בהוספת הערות לקוד.
טענותיהם של הנמנים עם אסכולת צמצום ההערות מגוונות יחסית:
האמת, כרגיל, נמצאת איפשהו באמצע, ולא מתפקידה של מחברת זו להכריע בוויכוח הזה.
כן נזכיר כמה כללים בסיסיים שפחות או יותר מקובלים על כולם:
ניזכר בפונקציה help, שמטרתה להציג לנו תיעוד בנוגע לערך מסוים בתוכנית:
In [ ]:
quote = "So many books, so little time."
help(quote.upper)
והיא תעבוד היטב, לשמחתנו, גם עבור סוג המשתנה str עצמו, אם כי בצורה טיפה פחות חיננית (פנו ערב כדי לקרוא את זה):
In [ ]:
quote = "So many books, so little time."
help(str)
אם ננסה להגדיר פונקציה משלנו, העברתה כארגומנט לפונקציה help תחזיר לנו מידע לא מועיל בעליל:
In [ ]:
def add(a, b):
return a + b
help(add)
אז מה עלינו לעשות כדי להוסיף תיעוד?
מתברר שזה לא כזה מסובך. בסך הכול צריך להוסיף משהו שנקרא "מחרוזת תיעוד".
נוסיף לפונקציה שלנו מחרוזת תיעוד של שורה אחת (One-line Docstring) בצורה הבאה:
In [ ]:
def add(a, b):
"""Return the result of a + b."""
return a + b
בדוגמה האחרונה הוספנו לשורה הראשונה של גוף הפונקציה מחרוזת תיעוד (Docstring), שמתחילה ומסתיימת ב־3 מירכאות כפולות.
אפשר להשתמש בסוג מירכאות אחר, אך 3 מירכאות זו המוסכמה ואנחנו נדבוק בה.
בין המירכאות תיארנו בקצרה מה הפונקציה עושה.
כעת הפונקציה help תשתף איתנו פעולה, ונוכל לקבל את התיעוד על הפונקציה שכתבנו:
In [ ]:
help(add)
נקודות חשובות בהקשר זה:
ניקח לדוגמה פונקציה שמקבלת נתיב ומחזירה את חלקיו:
In [ ]:
def get_parts(path):
current_part = ""
for char in self.fullpath:
if char in r"\/":
yield current_part
current_part = ""
else:
current_part = current_part + char
if current_part != "":
yield current_part
בתור התחלה, נוסיף לפונקציה מחרוזת תיעוד של שורה אחת שמתארת בקצרה מה תכליתה.
In [ ]:
def get_parts(path):
"""Split the path, return each part separately."""
current_part = ""
for char in self.fullpath:
if char in r"\/":
yield current_part
current_part = ""
else:
current_part = current_part + char
if current_part != "":
yield current_part
כדי להפוך את התיעוד למרובה שורות, נוסיף שורה ריקה אחרי השורה שמתארת בקצרה מה עושה הפונקציה.
אחרי השורה הריקה נוסיף תיאור כללי יותר שמסביר מה אפשר לצפות שהפונקציה תחזיר, ומה הפרמטרים שהיא מצפה לקבל.
המירכאות הסוגרות יזכו בשורה משלהן:
In [ ]:
def get_parts(path):
"""Split the path, return each part separately.
Each "part" of the path can be defined as a drive, folder, or
file, separated by a forward slash (/, typically used in Linux/Mac)
or by a backslash (usually used in Windows).
path -- String that consists of a drive (if applicable), folders
and files, separated by a forward slash or by a backslash.
"""
current_part = ""
for char in self.fullpath:
if char in r"\/":
yield current_part
current_part = ""
else:
current_part = current_part + char
if current_part != "":
yield current_part
סוג כזה של תיעוד נקרא "מחרוזת תיעוד מרובת שורות" (Multi-line Docstring).
נכתוב אותו כדי לעזור למי שישתמש בפונקציה להבין מה מטרתה, אילו פרמטרים היא מצפה לקבל ואילו ערכים יוחזרו ממנה.
היכן נשתמש בתיעוד מרובה שורות?
__init__
.
In [ ]:
"""A demonstration of writing well documented Python code.
This snippet demonstrates how a well documented code should look.
Each method and class is documented, and there is also
documentation for the script itself.
"""
import os
class Path:
"""Represent a filesystem path.
It is used to simplify the work with paths across different
operating systems. The initialization method takes a string and
populates the full path property along with "parts," which is a
version of the path after we split it using path separator
characters.
Basic Usage:
>>> Path(r'C:\Yossi').get_drive_letter()
'C:'
>>> str(Path(r'C:\Messed/Up/Path\To\file.png'))
'C:/Messed/Up/Path/To/file.png'
"""
def __init__(self, path):
self.fullpath = path
self.parts = list(self.get_parts())
def get_parts(self):
"""Split the path, return each part separately.
Each "part" of the path can be defined as a drive, folder, or
file, separated by a forward slash (/, typically used in
Linux/Mac) or by a backslash (usually used in Windows).
path -- String that consists of a drive (if applicable),
folders, and files, separated by a forward slash or by
a backslash.
"""
current_part = ""
for char in self.fullpath:
if char in r"\/":
yield current_part
current_part = ""
else:
current_part = current_part + char
if current_part != "":
yield current_part
def get_drive_letter(self):
"""Return the drive letter of the path, when applicable."""
return self.parts[0].rstrip(":")
def get_dirname(self):
"""Return the full path without the last part."""
path = "/".join(self.parts[:-1])
return Path(path)
def get_basename(self):
"""Return the last part of the path."""
return self.parts[-1]
def get_extension(self):
"""Return the extension of the filename.
If there is no extension, return an empty string.
This does not include the leading period.
For example: 'txt'
"""
name = self.get_basename()
i = name.rfind('.')
if 0 < i < len(name) - 1:
return name[i + 1:]
return ''
def is_exists(self):
"""Check if the path exists, return boolean value."""
return os.path.exists(str(self))
def normalize_path(self):
"""Create a normalized string of the path for printing."""
normalized = "\\".join(self.parts)
return normalized.rstrip("\\")
def info_message(self):
"""Return a long string with essential details about the file.
The string contains:
- Normalized path
- Drive letter
- Dirname
- Basename
- File extension (displayed even if not applicable)
- If the file exists
Should be used to easily print the details about the path.
"""
return f"""
Some info about "{self}":
Drive letter: {self.get_drive_letter()}
Dirname: {self.get_dirname()}
Last part of path: {self.get_basename()}
File extension: {self.get_extension()}
Is exists?: {self.is_exists()}
""".strip()
def __str__(self):
return self.normalize_path()
EXAMPLES = (
r"C:\Users\Yam\python.jpg",
r"C:/Users/Yam/python.jpg",
r"C:",
r"C:\\",
r"C:/",
r"C:\Users/",
r"D:/Users/",
r"C:/Users",
)
for example in EXAMPLES:
path = Path(example)
print(path.info_message())
print()
כאשר אנחנו מוסיפים לישות מסוימת מחרוזת תיעוד, נוספת לה תכונת קסם בשם __doc__
שמכילה את התיעוד שלה.
התוכן של התכונה הזו הוא זה שמודפס כאשר אנחנו מפעילים את הפונקציה help.
נבחן לדוגמה את תוכן התכונה __doc__
של quote.upper שסקרנו בתחילת המחברת.
אפשר לראות שהיא זהה לחלוטין למחרוזת שקיבלנו כשהפעלנו עליה help:
In [ ]:
help(quote.upper)
In [ ]:
print(quote.upper.__doc__)
ומה קורה כשניצור פונקציה משלנו?
ננסה ליצור לדוגמה את ידידתנו הוותיקה, הפונקציה add:
In [ ]:
def add(a, b):
return a + b
כיוון שלא הוספנו לפונקציה תיעוד, התכונה __doc__
תוגדר כ־None
:
In [ ]:
print(add.__doc__)
נוסיף תיעוד ונראה את השינוי:
In [ ]:
def add(a, b):
"""Return the result of a + b."""
return a + b
print(add.__doc__)
מדובר בידע כללי מגניב, אבל כאנשים מתורבתים לעולם לא נבצע השמה ישירה ל־__doc__
ולא ניגש אליה ישירות.
עם הזמן ראתה קהילת המפתחים בפייתון כי טוב, וחשבה על דרך נעימה ונוחה יותר לקרוא תיעוד ולכתוב אותו.
כיוון שתיעוד הוא חלק חשוב בקוד, התפתחו במרוצת השנים טכנולוגיות ותקנים שמטרתם לסייע למפתחי פייתון לתעד טוב יותר את הקוד שלהם.
תחילה, קהילת פייתון חיפשה דרך לכתוב משהו שהוא יותר מ"סתם מלל".
לעיתים קרובות נרצה להדגיש דברים בתיעוד, לתת קישור למקור חיצוני, להוסיף כותרת או לציין פריטים ברשימה.
שפת הסימון המוכרת HTML שמשומשת תדיר ליצירת דפי אינטרנט הייתה כבר קיימת, אבל בפייתון חיפשו שפה נקייה יותר שנוח לעין לסרוק.
לצורך כך פותחה בשנת 2002 שפת הסימון reStructuredText (או בקיצור – reST), שמאפשרת להפוך מלל שאנחנו כותבים לטקסט מסוגנן.
מטרתה העיקרית של reST הייתה לאפשר הכנסת טקסט מסוגנן לתיעוד טכני בפייתון, ופייתון אכן אימצה אותה רשמית לצורך זה.
היא מאפשרת לכתוב לא רק תיעוד בקוד, אלא גם תיעוד מילולי כללי על מטרותיו של הפרויקט ועל דרך השימוש בו (כמו ספר תיעוד קטן).
קצרה היריעה מלכלול פה הדרכה על שימוש ב־reStructuredText, אבל אנחנו ממליצים בחום לקרוא את המדריך המקוצר שנמצא כאן.
מלל שנכתב ב־reStructuredText ייראה כך עבור מי שכתב אותו:
אפשר לראות בטקסט *הזה* טעימה קטנה מהיכולות של **reStructuredText**. חלק מהאפשרויות הפחות מתוחכמות שלו כוללות: * הדגשה, מלל מוטה וקו תחתון. * רשימות. * סימון של קוד, כמו `print("Hello World")`.
וייראה כך בתוצאה הסופית:
אפשר לראות בטקסט הזה טעימה קטנה מהיכולות של reStructuredText.
חלק מהאפשרויות הפחות מתוחכמות שלו כוללות:
print("Hello World")
.
בשנת 2008 פותח בקהילת הפייתון כלי בשם Sphinx.
מטרתו לסרוק את התיעוד של פרויקט הקוד שלכם, וליצור ממנו מסמך תיעוד שנעים לקרוא ב־PDF או ב־HTML.
Sphinx, כמובן, תומך במסמכים שנכתבו ב־reStructuredText, והוא הפך במהרה לפופולרי מאוד.
אתר התיעוד הנוכחי של פייתון נוצר באמצעותו.
בהמשך הקורס נכתוב פרויקטים, ובהם נוכל להשתמש ב־Sphinx ליצירת מסמכים שיעזרו למשתמשים בפרויקט להתמצא בו.
משתמשים בולטים ב־Sphinx כוללים, בין השאר, את:
בשנת 2010 פותח אתר בשם Read the Docs, שמטרתו לרכז תיעוד לפרויקטים שנכתבו בפייתון.
האתר מאפשר להעלות לרשת בקלות תיעודים שנוצרו בעזרת Sphinx ולהנגיש אותם לקהל הרחב.
משתמשים בולטים ב־Read the Docs כוללים, בין השאר, את:
במשך שנים חיפשו מפתחי פייתון דרך אחידה יותר לכתוב מחרוזות תיעוד.
בכלל, מתכנתים אוהבים שלדברים יש צורה מוסכמת ומוגדרת מראש.
בעקבות הצורך הזה, התחילו להתפתח סגנונות שמגדירים בצורה הדוקה יותר כיצד אמור להיראות התוכן של מחרוזת תיעוד.
למה זה טוב, אתם שואלים? כי כשלדברים יש תקן אחיד אפשר לעשות הרבה דברים מגניבים:
מובן שכמפתחים לא באמת הצלחנו לוותר על הוויכוחים האינטרנטיים, ולכן הנקודה האחרונה לא תקפה.
עם הזמן התגבשו שלושה סגנונות פופולריים ל"איך אמורה להראות מחוזת תיעוד".
נבחן את ההבדלים בין הסגנונות, ונשאיר לכם לבחור באיזה סגנון תעדיפו להשתמש.
לפניכם קוד נחמד וקצרצר שאנחנו הולכים לתעד בשארית המחברת, בכל אחד מהסגנונות הללו.
בקוד נגדיר מחלקה של סניף דואר, שתאפשר למשתמשים בה לשלוח הודעות זה לזה.
ללא תיעוד כלל, המחלקה תיראה כך:
In [ ]:
class PostOffice:
def __init__(self, usernames):
self.message_id = 0
self.boxes = {user: [] for user in usernames}
def send_message(self, sender, recipient, message_body, urgent=False):
user_box = self.boxes[recipient]
self.message_id = self.message_id + 1
message_details = {
'id': self.message_id,
'body': message_body,
'sender': sender,
}
if urgent:
user_box.insert(0, message_details)
else:
user_box.append(message_details)
return self.message_id
וכך תיראה דוגמה לפונקציה מתועדת שמדגימה כיצד המחלקה עובדת:
In [ ]:
def show_example():
"""Show example of using the PostOffice class."""
users = ('Newman', 'Mr. Peanutbutter')
post_office = PostOffice(users)
message_id = post_office.send_message(
sender='Mr. Peanutbutter',
recipient='Newman',
message_body='Hello, Newman.',
)
print(f"Successfuly sent message number {message_id}.")
print(post_office.boxes['Newman'])
show_example()
מבולבלים? איזה מזל שאנחנו הולכים לתעד את המחלקה הזו.
קדימה, לעבודה.
גוגל מתחזקים זה שנים רבות מסמך מוסכמות ארוך משלהם שמפרט את סגנון הכתיבה הפנימי הרצוי בפייתון בחברה.
במסמך, בין היתר, מתארים גוגל כיצד הם מאמינים שצריכות להיראות מחרוזות תיעוד.
תוכלו לראות דוגמה לאופן שבו אמורות להיראות מחרוזות התיעוד של Google כאן.
נראה דוגמה לתיעוד המחלקה PostOffice ופעולותיה בשיטה של גוגל, ומייד אחר כך ננתח מה ראינו.
In [ ]:
class PostOffice:
"""A Post Office class. Allows users to message each other.
Args:
usernames (list): Users for which we should create PO Boxes.
Attributes:
message_id (int): Incremental id of the last message sent.
boxes (dict): Users' inboxes.
"""
def __init__(self, usernames):
self.message_id = 0
self.boxes = {user: [] for user in usernames}
def send_message(self, sender, recipient, message_body, urgent=False):
"""Send a message to a recipient.
Args:
sender (str): The message sender's username.
recipient (str): The message recipient's username.
message_body (str): The body of the message.
urgent (bool, optional): The urgency of the message.
Urgent messages appear first.
Returns:
int: The message ID, auto incremented number.
Raises:
KeyError: If the recipient does not exist.
Examples:
After creating a PO box and sending a letter,
the recipient should have 1 message in the
inbox.
>>> po_box = PostOffice(['a', 'b'])
>>> message_id = po_box.send_message('a', 'b', 'Hello!')
>>> len(po_box.boxes['b'])
1
>>> message_id
1
"""
user_box = self.boxes[recipient]
self.message_id = self.message_id + 1
message_details = {
'id': self.message_id,
'body': message_body,
'sender': sender,
}
if urgent:
user_box.insert(0, message_details)
else:
user_box.append(message_details)
return self.message_id
לפי מסמך הסגנון של גוגל, לפעולה צריכים להיות 3 חלקי תיעוד:
יש לתעד גם מחלקות כמובן:
__init__
של המחלקה.
NumPy היא המודול המוביל בפייתון בכל הקשור לכלים מתמטיים.
שיטת התיעוד שלו די דומה לזו של גוגל, ומתועדת כאן.
היא מעט קריאה יותר לעין אנושית, אך משתמשת ביותר שטח לאורך הדף:
In [ ]:
class PostOffice:
"""A Post Office class. Allows users to message each other.
Parameters
----------
usernames : list
Users for which we should create PO Boxes.
Attributes
----------
message_id : int
Incremental id of the last message sent.
boxes : dict
Users' inboxes.
"""
def __init__(self, usernames):
self.message_id = 0
self.boxes = {user: [] for user in usernames}
def send_message(self, sender, recipient, message_body, urgent=False):
"""Send a message to a recipient.
Parameters
----------
sender : str
The message sender's username.
recipient : str
The message recipient's username.
message_body : str
The body of the message.
urgent : bool, optional
The urgency of the message.
Urgent messages appear first.
Returns
-------
int
The message ID, auto incremented number.
Raises
------
KeyError
If the recipient does not exist.
Examples
--------
After creating a PO box and sending a letter,
the recipient should have 1 messege in the
inbox.
>>> po_box = PostOffice(['a', 'b'])
>>> message_id = po_box.send_message('a', 'b', 'Hello!')
>>> len(po_box.boxes['b'])
1
>>> message_id
1
"""
user_box = self.boxes[recipient]
self.message_id = self.message_id + 1
message_details = {
'id': self.message_id,
'body': message_body,
'sender': sender,
}
if urgent:
user_box.insert(0, message_details)
else:
user_box.append(message_details)
return self.message_id
מעבר להיותו כלי ליצירת מסמכי תיעוד, ב־Sphinx קיימת גם הגדרה לצורה שבה לדעתם מחרוזות תיעוד אמורות להיראות.
בלי לחץ – Sphinx ידע להמיר את התיעוד שלכם למסמך גם אם תשתמשו ב־Google Docstrings או ב־NumPy Docstrings.
סגנון זה תופס את השטח המזערי ביותר לאורך הדף, אך הוא מעט קשה יותר לקריאה.
מחרוזות התיעוד ש־Sphinx מגדירים נראות כך:
In [ ]:
class PostOffice:
"""A Post Office class. Allows users to message each other.
:ivar int message_id: Incremental id of the last message sent.
:ivar dict boxes: Users' inboxes.
:param list usernames: Users for which we should create PO Boxes.
"""
def __init__(self, usernames):
self.message_id = 0
self.boxes = {user: [] for user in usernames}
def send_message(self, sender, recipient, message_body, urgent=False):
"""Send a message to a recipient.
:param str sender: The message sender's username.
:param str recipient: The message recipient's username.
:param str message_body: The body of the message.
:param urgent: The urgency of the message.
:type urgent: bool, optional
:return: The message ID, auto incremented number.
:rtype: int
:raises KeyError: if the recipient does not exist.
"""
user_box = self.boxes[recipient]
self.message_id = self.message_id + 1
message_details = {
'id': self.message_id,
'body': message_body,
'sender': sender,
}
if urgent:
user_box.insert(0, message_details)
else:
user_box.append(message_details)
return self.message_id
למדנו על ההבדל בין הערות לבין תיעוד, על הגישות השונות אליהם ומתי להשתמש בכל אחד מהם.
סקרנו קצת את התפתחות רעיון התיעוד לאורך השנים בפייתון, ואת הכלים המשמשים את הקהילה בתהליך יצירת התיעוד.
למדנו גם על סגנונות תיעוד פופולריים, שיאפשרו לכם לכתוב תיעוד קריא בקוד שלכם, ובעתיד גם ליצור מסמכי תיעוד בעצמכם.
ממשו שתי פעולות נוספות למחלקת PostOffice:
חלק ממטרת התרגיל היא תרגול היכולת שלכם להיכנס לקוד קיים.
נסו לשנות את הקוד הקיים במידה, והסבירו את השינויים שלכם אם לדעתכם עולה צורך כזה.
ודאו שהקוד שלכם מתועד היטב.
ממשו מחלקה בשם Player שיודעת לקבל שם שחקן, וליצור שחקן חדש.
לכל שחקן יש לפחות את התכונות הבאות:
אם בעקבות פעולת התקפה שחקן מסוים הגיע ל־0 חיים או פחות מכך, הוא נחשב למת ברובו.
דאגו שהשחקן יעבור החייאה וצרפו את מי שהתקיף אותו לרשימת ה־nemeses של השחקן.
תעדו את התוכנית שלכם היטב.