Skip to content

Commit 9222df2

Browse files
committed
lab02
1 parent 283b104 commit 9222df2

File tree

1 file changed

+0
-194
lines changed

1 file changed

+0
-194
lines changed

docs/labs/lab-02.md

Lines changed: 0 additions & 194 deletions
Original file line numberDiff line numberDiff line change
@@ -547,201 +547,7 @@ def calculate_net_salary(base_salary, overtime_hours):
547547
salary = calculate_net_salary(5000, 10)
548548
print(f"Net salary: RM{salary:.2f}")
549549
```
550-
print(f"Final price: RM{final_price:.2f}")
551-
```
552-
553-
Now try changing `return` to `print` in the function. What happens? You'll get an error because you can't subtract `None` from a number!
554-
555-
## Factorization: Don't Repeat Yourself
556-
557-
Factorization is the process of taking repeated code and extracting it into a reusable function. This is one of the most important principles in programming.
558-
559-
### Identifying Repetition
560-
561-
Look for these signs that indicate you need factorization:
562-
1. You're copy-pasting code blocks
563-
2. The same calculation appears multiple times
564-
3. Only the values change, but the logic stays the same
565-
566-
Copy this example into your `exercise.py`:
567-
568-
```python
569-
# Calculating areas without factorization
570-
pi = 3.14159
571-
572-
# Circle 1
573-
radius1 = 5
574-
area1 = pi * radius1 * radius1
575-
print(f"Circle 1 (radius {radius1}): Area = {area1:.2f}")
576-
577-
# Circle 2
578-
radius2 = 10
579-
area2 = pi * radius2 * radius2
580-
print(f"Circle 2 (radius {radius2}): Area = {area2:.2f}")
581-
582-
# Circle 3
583-
radius3 = 7
584-
area3 = pi * radius3 * radius3
585-
print(f"Circle 3 (radius {radius3}): Area = {area3:.2f}")
586-
```
587-
588-
Run this code. Notice how `pi * radius * radius` appears three times? This is a perfect candidate for factorization.
589-
590-
### Refactoring with Functions
591-
592-
Refactoring means improving code structure without changing what it does. Replace the code above with this factorized version:
593-
594-
```python
595-
def calculate_circle_area(radius):
596-
pi = 3.14159
597-
area = pi * radius * radius
598-
return area
599-
600-
# Now the calculation is defined once and reused
601-
area1 = calculate_circle_area(5)
602-
print(f"Circle 1 (radius 5): Area = {area1:.2f}")
603-
604-
area2 = calculate_circle_area(10)
605-
print(f"Circle 2 (radius 10): Area = {area2:.2f}")
606-
607-
area3 = calculate_circle_area(7)
608-
print(f"Circle 3 (radius 7): Area = {area3:.2f}")
609-
```
610-
611-
The benefits are immediate:
612-
- If you need to calculate 100 circles, you just call the function 100 times
613-
- If you need a more precise value of pi (3.14159265), you change only one line
614-
- The code is shorter and easier to read
615-
616-
## Single Responsibility Principle
617-
618-
Each function should have **one** clear job. When a function tries to do too many things, it becomes difficult to understand, test, and modify.
619-
620-
### Recognizing Multiple Responsibilities
621-
622-
A function has multiple responsibilities if it:
623-
- Gets input AND processes it AND displays output
624-
- Performs multiple unrelated calculations
625-
- Makes decisions AND displays results
626-
627-
Try this example:
628-
629-
```python
630-
def process_student():
631-
# Responsibility 1: Getting input
632-
name = input("Enter student name: ")
633-
score = int(input("Enter score: "))
634-
635-
# Responsibility 2: Making decision
636-
if score >= 50:
637-
status = "Pass"
638-
else:
639-
status = "Fail"
640-
641-
# Responsibility 3: Displaying output
642-
print(f"Student: {name}")
643-
print(f"Score: {score}")
644-
print(f"Status: {status}")
645-
646-
# Run the function
647-
process_student()
648-
```
649-
650-
This function does three completely different jobs. What if you want to change how pass/fail is determined but keep the same input method? You'd have to dig through all the code.
651-
652-
### Splitting Responsibilities
653-
654-
A better approach is to split this into three focused functions:
655-
656-
```python
657-
def get_student_data():
658-
name = input("Enter student name: ")
659-
score = int(input("Enter score: "))
660-
return name, score
661-
662-
def determine_status(score):
663-
if score >= 50:
664-
return "Pass"
665-
else:
666-
return "Fail"
667-
668-
def display_results(name, score, status):
669-
print(f"Student: {name}")
670-
print(f"Score: {score}")
671-
print(f"Status: {status}")
672550

673-
# Use them together
674-
student_name, student_score = get_student_data()
675-
student_status = determine_status(student_score)
676-
display_results(student_name, student_score, student_status)
677-
```
678-
679-
Now each function has one clear purpose:
680-
- `get_student_data()` only handles input
681-
- `determine_status()` only makes the pass/fail decision
682-
- `display_results()` only shows information
683-
684-
If the passing grade changes to 60, you only modify `determine_status()`. The other functions remain untouched.
685-
686-
::: tip THE 5-WORD TEST
687-
If you can't describe what a function does in 5 words or less, it probably does too much and should be split.
688-
:::
689-
690-
## Decomposition: Breaking Down Complex Problems
691-
692-
Decomposition is the art of breaking a large, complex task into smaller, manageable functions. Instead of writing one massive function that does everything, you create several small functions that each handle one piece of the puzzle.
693-
694-
### Why Decompose?
695-
696-
Consider calculating an employee's monthly salary:
697-
1. Base salary
698-
2. Add allowances (transport, meal)
699-
3. Add overtime pay
700-
4. Subtract EPF (11%)
701-
5. Subtract SOCSO (2%)
702-
6. Calculate net salary
703-
704-
Writing this as one long calculation is error-prone and hard to understand. Instead, decompose it into steps.
705-
706-
### Step-by-Step Decomposition
707-
708-
Create separate functions for each calculation step:
709-
710-
```python
711-
def calculate_allowances():
712-
transport = 200.00
713-
meal = 150.00
714-
return transport + meal
715-
716-
def calculate_overtime(base_salary, overtime_hours):
717-
hourly_rate = base_salary / 160 # Assuming 160 working hours per month
718-
overtime_rate = hourly_rate * 1.5 # 1.5x for overtime
719-
return overtime_hours * overtime_rate
720-
721-
def calculate_epf(gross_salary):
722-
return gross_salary * 0.11
723-
724-
def calculate_socso(gross_salary):
725-
return gross_salary * 0.02
726-
727-
def calculate_net_salary(base_salary, overtime_hours):
728-
# Step 1: Calculate gross salary
729-
allowances = calculate_allowances()
730-
overtime = calculate_overtime(base_salary, overtime_hours)
731-
gross = base_salary + allowances + overtime
732-
733-
# Step 2: Calculate deductions
734-
epf = calculate_epf(gross)
735-
socso = calculate_socso(gross)
736-
737-
# Step 3: Calculate net
738-
net = gross - epf - socso
739-
return net
740-
741-
# Use it
742-
salary = calculate_net_salary(5000, 10)
743-
print(f"Net salary: RM{salary:.2f}")
744-
```
745551

746552
Each function is simple and focused. If EPF rate changes, you only modify `calculate_epf()`. If overtime rules change, you only modify `calculate_overtime()`.
747553

0 commit comments

Comments
 (0)