Know Your Next Commit

本文通过三个程序员的工作实例,探讨了在软件开发过程中如何保持专注并有效完成任务的方法。强调了明确目标、小步快跑的重要性,并指出盲目投入可能导致代码质量下降的问题。

I tapped three programmers on their shoulders and asked what they were doing. "I am refactoring these methods," the first answered. "I am adding some parameters to this web action," the second answered. The third answered, "I am working on this user story."

It might seem that the first two were engrossed in the details of their work while only the third could see the bigger picture, and that the latter had the better focus. However, when I asked when and what they would commit, the picture changed dramatically. The first two where pretty clear over what files would be involved and would be finished within an hour or so. The third programmer answered, "Oh, I guess I will be ready within a few days. I will probably add a few classes and might change those services in some way."

The first two did not lack a vision of the overall goal. They had selected tasks they thought led in a productive direction, and could be finished within a couple of hours. Once they had finished those tasks, they would select a new feature or refactoring to work on. All the code written was thus done with a clear purpose and a limited, achievable goal in mind.

The third programmer had not been able to decompose the problem and was working on all aspects at once. He had no idea of what it would take, basically doing speculative programming, hoping to arrive at some point where he would be able to commit. Most probably the code written at the start of this long session was poorly matched for the solution that came out in the end.

What would the first two programmers do if their tasks took more than two hours? After realizing they had taken on too much, they would most likely throw away their changes, define smaller tasks, and start over. To keep working would have lacked focus and led to speculative code entering the repository. Instead, changes would be thrown away, but the insights kept.

The third programmer might keep on guessing and desperately try to patch together his changes into something that could be committed. After all, you cannot throw away code changes you have done — that would be wasted work, wouldn't it? Unfortunately, not throwing the code away leads to slightly odd code that lacks a clear purpose entering the repository.

At some point even the commit-focused programmers might fail to find something useful they thought could be finished in two hours. Then, they would go directly into speculative mode, playing around with the code and, of course, throwing away the changes whenever some insight led them back on track. Even these seemingly unstructured hacking sessions have purpose: to learn about the code to be able to define a task that would constitute a productive step.

Know your next commit. If you cannot finish, throw away your changes, then define a new task you believe in with the insights you have gained. Do speculative experimentation whenever needed, but do not let yourself slip into speculative mode without noticing. Do not commit guesswork into your repository.

By Dan Bergh Johnsson

This work is licensed under a Creative Commons Attribution 3

Your library system is steadily evolving. Users can now log in, borrow, and return books with ease. But one problem remains: finding the right book isn't always so easy. The current search function only works if users know the exact title. Imagine being a stressed student during exam week, remembering only "something about machine learning" but not the full book title -- frustrating, right? The librarians want to fix this by introducing a keyword-based search, so users can explore the catalogue more naturally. On top of that, the university has just secured extra funding to expand the collection, and your system must now support adding brand-new books into the database. Task Description Updated Main Menu For students, staff (non-library) and other users, a new option, Search by Keywords (Option 5), is added to the main menu. Logged in as Chris Manner (Student) ================================== My Library Account 0. Quit 1. Log out 2. View account policies 3. View my loans 4. Borrow and Return 5. Search by Keywords ================================== Enter your choice: For library staff, alongside Search by Keywords (option 5), the existing Library Report option is expanded into Manage Library (option 6), offering more administrative features (described later). Logged in as Mary Alan (Staff) ================================== My Library Account 0. Quit 1. Log out 2. View account policies 3. View my loans 4. Borrow and Return 5. Search by Keywords 6. Manage Library ================================== Enter your choice: Renew loan All users may renew each borrowed book once, using the command renew <book ID> in the Borrow and Return console. Renewal extends the due date by 5 days. If the user has borrowed multiple copies of the same book, the system will renew the copy (or loan) with the earliest due date first. A book is not eligible for renewal if: Its extended due date has already passed (making it overdue). An error message "Renewal denied: This book is already overdue." will be printed out instead. If it is already renewed once by the user, the system will display "Renewal unavailable: Each book can only be renewed once." Users with unpaid fines cannot renew any loans until their fines are settled. The system will display "Renewal denied: You have unpaid fines." Returning validation priority is similar to borrowing validation priority in Task 2. The system first checks whether the user is eligible to renew. Then it verifies if the loan is valid and is eligible to renew. Logged in as Chris Manner (Student) ================================== My Library Account 0. Quit 1. Log out 2. View account policies 3. View my loans 4. Borrow and Return 5. Search by Keywords ================================== Enter your choice: 4 > renew P0011 Renewal denied: You have unpaid fines. An error will return if the loan record does not exist Logged in as Noah (Others) ================================== My Library Account 0. Quit 1. Log out 2. View account policies 3. View my loans 4. Borrow and Return 5. Search by Keywords ================================== Enter your choice: 4 > renew P0011 No loan record for P0011. Search by Keywords Upon entering 5, the system will prompt them to enter a list of keywords (case insensitive), separated by commas (,). The program will then search the catalogue and return a list of books that contain at least one of the specified keywords. The results will be sorted in the following order of priority: Number of matched keywords (highest first) Publication year (newest first) Book ID (ascending order) If the keyword list is empty, the program should return "Found 0 book(s)." Logged in as Noah (Others) ================================== My Library Account 0. Quit 1. Log out 2. View account policies 3. View my loans 4. Borrow and Return 5. Search by Keywords ================================== Enter your choice: 5 Enter search keywords (separated by comma): python,programming Found 7 book(s). 1. P0003 'Python Crash Course' by Eric Matthes (2023). 2. P0001 'Introduction to Python Programming' by S Gowrishankar (2019). 3. E0002 'Deep learning with Python: a hands-on introduction' by Ketkar Nikhil (2017). 4. E0001 'Python Crash Course' by Eric Matthes (2015). 5. P0002 'Python Programming: An Introduction to Computer Science' by John M. Zelle (2002). 6. E0003 'Machine Learning for Business' by Doug Hudgeon & Richard Nichol (2020). 7. P0006 'Hands-On ML' by Aurelien Geron (2019). Manage Library (Library Staff only) When staff members select option 6 to enter the Manage Library console, they can perform the following actions: Print library report by enter report Add a New Book Use the command add physical to add a new physical book, or add online to add a new online book. The system will then prompt the user to provide the book’s details, including title, authors, year, number of copies (required for physical books only) The system will automatically assign keywords to newly added books by matching any words in their titles with the existing keyword list from the books.csv dataset (sorted by alphabet order). Each new book is assigned a unique ID: IDs for physical books begin with P. IDs for online books begin with E. The numeric portion of the ID is generated by incrementing the highest existing ID of that type in the library. Type quit to exit the borrow and return console and go back to main menu. Logged in as Mary Alan (Staff) ================================== My Library Account 0. Quit 1. Log out 2. View account policies 3. View my loans 4. Borrow and Return 5. Search by Keywords 6. Manage Library ================================== Enter your choice: 6 > report Library Report - 9 users, including 4 student(s), 3 staff and 2 others. - 14 books, including 10 physical book(s) (7 currently available) and 3 online book(s). > add physical Title: A Concise and Practical Introduction to Programming Algorithms in Java Authors: Nielsen Frank Year: 2017 Copies: 1 Detected keywords: algorithms:programming Adding P0020 'A Concise and Practical Introduction to Programming Algorithms in Java' by Nielsen Frank (2017). Examples Example 1 Welcome to Library Login as: s31267 Password: chr1267 Logged in as Chris Manner (Student) ================================== My Library Account 0. Quit 1. Log out 2. View account policies 3. View my loans 4. Borrow and Return 5. Search by Keywords ================================== Enter your choice: 2 Student Chris Manner. Policies: maximum of 10 days, 4 items. Current loans: 2 (1 physical / 1 online). Fines: $ 1.00 ================================== My Library Account 0. Quit 1. Log out 2. View account policies 3. View my loans 4. Borrow and Return 5. Search by Keywords ================================== Enter your choice: 3 You are currently have 2 loan(s). 1. P0006 'Hands-On ML' by Aurelien Geron (2019). Due date: 13/09/2025. 2. E0001 'Python Crash Course' by Eric Matthes (2015). Due date: 15/09/2025. ================================== My Library Account 0. Quit 1. Log out 2. View account policies 3. View my loans 4. Borrow and Return 5. Search by Keywords ================================== Enter your choice: 4 > renew P0101 Renewal denied: You have unpaid fines. > return P0006 Returned 'Hands-On ML' by Aurelien Geron (2019). Overdue by 2 day(s). Fine: $ 1.00 > renew E0001 Renewal denied: This book is already overdue. > return E0001 Returned 'Python Crash Course' by Eric Matthes (2015). > borrow The Hobbit Found 1 book(s). - P0008 (physical) 'The Hobbit' by J.R.R. Tolkien (1937). Available copies: 1/2. Confirm the Book ID you'd like to borrow: P0008 You have borrowed 'The Hobbit' by J.R.R. Tolkien (1937). Due: 25/09/2025. > renew P0008 Renew 'The Hobbit' by J.R.R. Tolkien (1937) successfully. New due date: 30/09/2025 > quit ================================== My Library Account 0. Quit 1. Log out 2. View account policies 3. View my loans 4. Borrow and Return 5. Search by Keywords ================================== Enter your choice: 3 You are currently have 1 loan(s). 1. P0008 'The Hobbit' by J.R.R. Tolkien (1937). Due date: 30/09/2025. ================================== My Library Account 0. Quit 1. Log out 2. View account policies 3. View my loans 4. Borrow and Return 5. Search by Keywords ================================== Enter your choice: 0 Goodbye! Example 2 Welcome to Library Login as: o56799 Password: noa6799 Logged in as Noah (Others) ================================== My Library Account 0. Quit 1. Log out 2. View account policies 3. View my loans 4. Borrow and Return 5. Search by Keywords ================================== Enter your choice: 5 Enter search keywords (separated by comma): python,programming Found 7 book(s). 1. P0003 'Python Crash Course' by Eric Matthes (2023). 2. P0001 'Introduction to Python Programming' by S Gowrishankar (2019). 3. E0002 'Deep learning with Python: a hands-on introduction' by Ketkar Nikhil (2017). 4. E0001 'Python Crash Course' by Eric Matthes (2015). 5. P0002 'Python Programming: An Introduction to Computer Science' by John M. Zelle (2002). 6. E0003 'Machine Learning for Business' by Doug Hudgeon & Richard Nichol (2020). 7. P0006 'Hands-On ML' by Aurelien Geron (2019). ================================== My Library Account 0. Quit 1. Log out 2. View account policies 3. View my loans 4. Borrow and Return 5. Search by Keywords ================================== Enter your choice: 4 > borrow E0003 Found 1 book(s). - E0003 (online) 'Machine Learning for Business' by Doug Hudgeon & Richard Nichol (2020). Available copies: 0/0. Confirm the Book ID you'd like to borrow: E0003 You have borrowed 'Machine Learning for Business' by Doug Hudgeon & Richard Nichol (2020). Due: 22/09/2025. > quit ================================== My Library Account 0. Quit 1. Log out 2. View account policies 3. View my loans 4. Borrow and Return 5. Search by Keywords ================================== Enter your choice: 3 You are currently have 1 loan(s). 1. E0003 'Machine Learning for Business' by Doug Hudgeon & Richard Nichol (2020). Due date: 22/09/2025. ================================== My Library Account 0. Quit 1. Log out 2. View account policies 3. View my loans 4. Borrow and Return 5. Search by Keywords ================================== Enter your choice: 0 Goodbye! Example 3 Welcome to Library Login as: e118102 Password: pa55word Logged in as Mary Alan (Staff) ================================== My Library Account 0. Quit 1. Log out 2. View account policies 3. View my loans 4. Borrow and Return 5. Search by Keywords 6. Manage Library ================================== Enter your choice: 6 > report Library report - 9 users, including 4 student(s), 3 staff, and 2 others. - 14 books, including 10 physical book(s) (7 currently available) and 4 online book(s). > add physical Title: A Concise and Practical Introduction to Programming Algorithms in Java Authors: Nielsen Frank Year: 2017 Copies: 1 Detected keywords: algorithms:programming Adding P0020 'A Concise and Practical Introduction to Programming Algorithms in Java' by Nielsen Frank (2017). > report Library report - 9 users, including 4 student(s), 3 staff, and 2 others. - 15 books, including 11 physical book(s) (8 currently available) and 4 online book(s). > quit ================================== My Library Account 0. Quit 1. Log out 2. View account policies 3. View my loans 4. Borrow and Return 5. Search by Keywords 6. Manage Library ================================== Enter your choice: 0 Goodbye!import user import book import csv import datetime from datetime import timedelta import typing def parse_date(date_str: str) -> typing.Optional[datetime.date]: if not date_str or date_str.strip() == "None": return None day, month, year = map(int, date_str.split('/')) return datetime.date(year, month, day) def load_users(file_path: str) -> dict: users = {} with open(file_path, newline='') as csvfile: reader = csv.reader(csvfile) next(reader) for row in reader: user_id, password, name, role, department = row role = row[3].capitalize() if user_id.startswith('s'): u = user.Student(user_id, password, name, role, None) elif user_id.startswith('e'): u = user.Staff(user_id, password, name, role, department) else: role = "Others" u = user.Other(user_id, password, name, role, None) users[user_id] = u return users def load_books(file_path: str) -> dict: books = {} with open(file_path, newline='') as csvfile: reader = csv.reader(csvfile) next(reader) for row in reader: book_id, book_type, copies, title, author, year, keywords = row b = book.Book(book_id, book_type, int(copies), title, author, int(year), keywords) books[book_id] = b b.loans = [] return books def load_loans(file_path: str, users: dict, books: dict): loans = [] with open(file_path, newline='') as csvfile: reader = csv.reader(csvfile) next(reader) for row in reader: user_id, book_id, borrow_date, due_date, return_date = row if user_id in users and book_id in books: loan = Loan(users[user_id], books[book_id], borrow_date, due_date, return_date) users[user_id].add_loan(loan) books[book_id].loans.append(loan) loans.append(loan) return loans def borrow_return_console(user, books, users): TODAY = datetime.datetime.strptime("15/09/2025", "%d/%m/%Y").date() while True: cmd = input("> ").strip() if cmd == "quit": return part = cmd.split(' ', 1) if len(part) < 2: continue action, query = part[0].lower(), part[1] if action == "borrow": matched_books = [] for b in books.values(): if query.lower() in b.title.lower() or query == b.book_id: matched_books.append(b) matched_books.sort(key=lambda x: x.book_id) if not matched_books: print(f"No books match '{query}'.") continue physical_match = [b for b in matched_books if b.book_type == "physical"] online_match = [b for b in matched_books if b.book_type == "online"] available_str = sum(b.available_copies() for b in physical_match) print(f"Found {len(matched_books)} book(s).") all_matches = physical_match + online_match all_matches.sort(key=lambda x: x.book_id) for b in all_matches: copies = f"Available copies: {available_str}/{b.total_copies}" if b.book_type == "physical" else "Available copies: 0/0" print(f"- {b.book_id} ({b.book_type}) '{b.title}' by {b.authors} ({b.year}). {copies}.") valid_ids = [b.book_id for b in all_matches] while True: borrowid = input(f"Confirm the Book ID you'd like to borrow: ").strip() if borrowid == "quit": break if borrowid not in valid_ids: continue book = books[borrowid] unpaid_fine = sum(loan.calculate_fine() for loan in user.get_active_loans()) if unpaid_fine > 0: print("Borrowing unavailable: unpaid fines. Review your loan details for more info.") break current_physical = sum(1 for loan in user.get_active_loans() if loan.book.book_type == "physical") if current_physical >= user.get_loan_policy()["quota"]: print("Borrowing unavailable: quota reached. Review your loan details for more info.") break if book.book_type == "physical" and book.available_copies() <= 0: print("No copies available.") break due_days = user.get_loan_policy()["days"] due_date = TODAY + timedelta(days = due_days) borrow_str = TODAY.strftime("%d/%m/%Y") due_str = due_date.strftime("%d/%m/%Y") return_str = "None" loan = Loan(user, book, borrow_str, due_str, return_str) user.loans.append(loan) book.loans.append(loan) print(f"You have borrowed '{book.title}' by {book.authors} ({book.year}). Due: {due_date.strftime('%d/%m/%Y')}.") break elif action == "return": borrowid = query.strip() if borrowid not in books: print("No loan record for {}.".format(borrowid)) continue book = books[borrowid] active_loans = [loan for loan in user.get_active_loans() if loan.book.book_id == borrowid] if not active_loans: print(f"No loan record for {borrowid}.") continue loan_to_return = min(active_loans, key=lambda x: x.due_date) user.loans.remove(loan_to_return) return_date = TODAY fine_amount = 0.0 overdue_days = 0 if book.book_type == "physical" and return_date > loan_to_return.due_date: grace = {"Student": 0, "Staff": 2, "Others": 0}[user.role] effective_due = loan_to_return.due_date + timedelta(days = grace) overdue_days = max(0, (TODAY - effective_due).days) rate = 0.50 if user.role in ['Student', 'Staff'] else 1.00 fine_amount = overdue_days * rate status = f"Returned '{book.title}' by {book.authors} ({book.year})." if overdue_days > 0: status += f" Overdue by {overdue_days} day(s). Fine: $ {fine_amount:.2f}" print(status) else: continue class Loan: def __init__(self, user, book, borrow_str, due_str, return_str): self.user = user self.book = book self.borrow_date = parse_date(borrow_str) self.due_date = parse_date(due_str) self.return_date = parse_date(return_str) def calculate_fine(self): TODAY = datetime.datetime.strptime("15/09/2025", "%d/%m/%Y").date() if self.book.book_type == "online": return 0.0 if TODAY <= self.due_date: return 0.0 grace = {"Student": 0, "Staff": 2, "Others": 0}[self.user.role] effective_due = self.due_date + timedelta(days = grace) overdue_days = max(0, (TODAY - effective_due).days) rate = 0.50 if self.user.role in ['Student', 'Staff'] else 1.00 return overdue_days * rate def main(user_file: str, book_file:str, loan_file:str) -> None: """ This is the entry of your program. Please DO NOT modify this function signature, i.e. function name, parameters Parameteres: - user_file (str): path the `users.csv` which stores user information - book_file (str): path the `books.csv` which stores book information - loan_file (str): path the `loans.csv` which stores loan information """ # Your implemetation goes here users = load_users(user_file) books = load_books(book_file) load_loans(loan_file, users, books) while True: print("Welcome to Library") user_input = input("Login as: ").strip() if user_input == "quit": print("Goodbye!") return else: password = input("Password: ").strip() if user_input not in users or users[user_input].password != password: print("Invalid credentials. 2 attempt(s) remaining.") user_input = input("Login as: ").strip() password = input("Password: ").strip() if user_input not in users or users[user_input].password != password: print("Invalid credentials. 1 attempt(s) remaining.") user_input = input("Login as: ").strip() password = input("Password: ").strip() if user_input not in users or users[user_input].password != password: print("Sorry you're out of attempts. Please contact your librarian for assistance.") continue current_user = users[user_input] print(f"Logged in as {current_user.name} ({current_user.role})") while True: menu = [ "My Library Account", "0. Quit", "1. Log out", "2. View account policies", "3. View my loans", "4. Borrow and Return" ] if current_user.is_library_staff(): menu.append("5. Library Report") print("=" * 34) for item in menu: print(item) print("=" * 34) while True: try: choice = input("Enter your choice: ").strip() if not choice.isdigit() or choice not in ['0', '1', '2', '3', '4', '5']: continue choice = int(choice) break except: continue if choice == 0: print("Goodbye!") return elif choice == 1: break elif choice == 2: policy = current_user.get_loan_policy() active_loans = current_user.get_active_loans() num_loans = len(active_loans) phys, online = current_user.get_physical_online_count() total_fine = sum(loan.calculate_fine() for loan in active_loans) print(f"{current_user.role} {current_user.name}. Policies: maximum of {policy['days']} days, {policy['quota']} items. Current loans: {num_loans} ({phys} physical / {online} online). Fines: $ {total_fine:.2f}") elif choice == 3: active_loans = sorted(current_user.get_active_loans(), key=lambda x: x.due_date) count = len(active_loans) print(f"You are currently have {count} loan(s).") for i, loan in enumerate(active_loans, 1): print(f"{i}. {loan.book.book_id} '{loan.book.title}' by {loan.book.authors} ({loan.book.year}). Due date: {loan.due_date.strftime('%d/%m/%Y')}.") elif choice == 4: borrow_return_console(current_user, books, users) elif choice == 5: total_user = len(users) students = sum(1 for u in users.values() if u.user_id.startswith('s')) staffs = sum(1 for u in users.values() if u.user_id.startswith('e')) others = sum(1 for u in users.values() if u.user_id.startswith('o')) total_books = len(books) physical_books = sum(1 for b in books.values() if b.book_type == "physical") available_physical = sum(1 for b in books.values() if b.book_type == "physical" and b.is_available()) online_books = sum(1 for b in books.values() if b.book_type == "online") print("Library report") print(f"- {total_user} users, including {students} student(s), {staffs} staff, and {others} others.") print(f"- {total_books} books, including {physical_books} physical book(s) ({available_physical} currently available) and {online_books} online book(s).") else: continue if __name__ == "__main__": main('data/users.csv', 'data/books.csv', 'data/loans.csv') import csv import datetime import re class Book: # your code goes here def __init__(self, book_id: str, book_type: str, total_copies: int, title: str, authors: str, year: int, keywords: str): self.book_id = book_id self.book_type = book_type self.total_copies = total_copies self.title = title self.authors = authors self.year = year self.keywords = keywords.split(':') if keywords else [] self.loans = [] def available_copies(self): borrowed_count = sum(1 for loan in self.loans if loan.return_date is None) return self.total_copies - borrowed_count def is_available(self) -> bool: if self.book_type == "online": return True borrowed_count = sum(1 for loan in self.loans if loan.return_date is None) return borrowed_count < self.total_copies def __str__(self): return f"{self.title} by {self.authors} ({self.year})"from abc import ABC, abstractmethod import csv import datetime import re TODAY = "15/09/2025" class User(ABC): # Your code goes here def __init__(self, user_id: str, password: str, name: str, role: str, department: str = None): self.user_id = user_id self.password = password self.name = name self.role = role self.department = department self.loans = [] @abstractmethod def get_loan_policy(self): pass def is_library_staff(self) -> bool: return self.role == "Staff" and self.department == "Library" def add_loan(self, loan): self.loans.append(loan) def get_active_loans(self): return [loan for loan in self.loans if loan.return_date is None] def get_physical_online_count(self): physical = sum(1 for loan in self.get_active_loans() if loan.book.book_type == "physical") online = sum(1 for loan in self.get_active_loans() if loan.book.book_type == "online") return physical, online def __str__(self): return f"{self.name} ({self.role})" class Student(User): def get_loan_policy(self): return {"days": 10, "quota": 4} class Staff(User): def get_loan_policy(self): return {"days": 14, "quota": 6} class Other(User): def get_loan_policy(self): return {"days": 7, "quota": 2}
10-14
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值