You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Lesson: Benefits of Small Commits (with unit-tests included)
Introduction
In this lesson, we'll explore the benefits of working with small commits and
maintaining functional code at every step. We'll use a simple TypeScript calculator project and
add basic operations one by one, pairing each change with its corresponding unit test using Jest.
After completing the initial development, we'll introduce git bisect to demonstrate how small commits
make debugging easier.
The calculator project should reflect the same stack we're using: Typescript, and jest.
The code should be added into a src folder which can be built using typescipt, and the scripts in package.json
should also reflect the same scripts we usually work with in our repos (build, start, test, and test:watch)
Ofc this example is minimal to show the git changes more clearly and our real application code usually is harder
to apply these techniques, but the examples in this lesson should give you a starting point on how to approach this.
Creating sub-tasks in Jira effectively can help streamline your development process and promote small PRs.
Here are some best practices for sub-tasks to help with that.
Break Down Stories/Tasks into Smaller Sub-Tasks
Identify Components: Break the main task into smaller, manageable components that can be individually developed and tested.
Clear Definition of Done: Each sub-task should have a clear definition of done to ensure it is completed properly before moving on.
Logical Progression: Ensure sub-tasks follow a logical sequence that mirrors the development workflow.
Set Clear Goals for Each Sub-Task
Single Responsibility Principle: Each sub-task should focus on a single aspect or feature. Avoid combining multiple changes in one sub-task.
Detailed Descriptions: Provide detailed descriptions, acceptance criteria, and relevant documentation for each sub-task to avoid ambiguity.
Time-box Sub-Tasks
Effort Estimation: Estimate the effort required for each sub-task and ensure it’s small enough to complete in a short period, ideally within a day or two.
Review and Adjust: Regularly review and adjust the scope of sub-tasks to keep them small and manageable.
Prioritize and Sequence Sub-Tasks
Dependencies: Identify and document any dependencies between sub-tasks. Ensure they are prioritized and sequenced accordingly.
Critical Path: Focus on the critical path first—sub-tasks that unblock other work should be prioritized.
Collaborate and Communicate:
Team Discussions: Involve the team in the breakdown process to ensure everyone understands the scope and goals of each sub-task.
Regular Updates: Maintain open communication about the progress of sub-tasks. Use comments and status updates in Jira to keep everyone informed.
Integrate with Development Workflow:
Branching Strategy: Align your branching strategy with Jira sub-tasks. Create separate branches for each sub-task to facilitate small, focused PRs.
Review and Refine:
Retrospectives: Conduct regular retrospectives to review how well the sub-task breakdown is working and identify areas for improvement.
Feedback Loop: Encourage feedback from the team on the size and scope of sub-tasks to continually refine the process.
Summary: BASICS for Creating Effective Sub-Tasks
BASICS is an easy-to-remember acronym for creating effective sub-tasks in Jira to promote small PRs.
Letter
Step Name
Step Description
B
Break Down Tasks
Divide main tasks into manageable sub-tasks.
A
Assign Clear Goals
Ensure each sub-task has a specific goal and detailed description.
S
Set Time-Box
Keep sub-tasks small and quickly completable.
I
Identify Dependencies
Document and prioritize dependencies.
C
Collaborate
Discuss and update progress with the team regularly.
S
Sequence and Integrate
Align branching strategy with sub-ttasks and use PR templates.
By following the BASICS framework, you can streamline your development process and promote small, manageable PRs.
Each commit includes tests, ensuring that new code works as expected.
Issues are caught early.
Easy Reverts:
If a bug is introduced, you can revert specific commits without losing unrelated work.
Clean History:
A clear and understandable commit history makes it easier to track changes and debug issues.
Some techniques and best practices
Planning in Advance: As discussed in the part 02, breaking down features into smaller,
manageable sub-tasks before starting development, makes each sub-task correspond to a single branch and PR.
Create Branches Early: As soon as you finish a specific sub-task, create a separate branch for the next sub-task or sub-feature.
This helps in isolating changes and makes it easier to create focused PRs. After the 1st branch is merged to develop,
you should be able to rebase the 2nd branch onto the develop branch and open the next PR when you finish that branch.
Single Responsibility:
Each commit should have a single responsibility, making it easier to understand and review.
Avoid mixing different types of changes (e.g., feature implementation, refactoring, and bug fixes) in a single commit.
Descriptive Commit Messages:
Use clear and descriptive commit messages that summarize the purpose of the commit.
Follow a consistent commit message format, such as the semantic commit structure, to maintain clarity and organization in the commit history.
Test-Driven Development: Write tests alongside code changes to ensure that each commit is functional and verifiable.
Commit Empty New Files with Basic Wrappers for new files: Start by committing empty files with basic class or
function wrappers and test descriptions. This approach makes it easier to cherry-pick commits and reorder changes as needed.
Jest supports the test.todo feature, allowing you to write placeholders for tests that you plan to implement later.
This is useful for outlining the tests in advance and ensuring that the structure of your test suite is ready.
Example:
// tests/calculator.test.tsimport{Calculator}from'../src/calculator';describe('Calculator',()=>{test.todo('adds 2 + 3 to equal 5');test.todo('subtracts 5 - 3 to equal 2');test.todo('multiplies 2 * 3 to equal 6');test.todo('divides 6 / 3 to equal 2');});
After files are created:
Pairing the implementation of a method with its corresponding test in the same commit ensures that each commit is both functional and verifiable.
This practice reinforces the habit of writing tests alongside code changes, promoting a test-driven development (TDD) approach.
if this still creates a big PR, implementation and tests can be splitted into 2 different commits for a single metho, but should generally be sequencial commits in order to verify the new code ASAP in the same commit sequence
Summary: B.R.A.N.C.H. for Developing with Small Commits and Unit Tests
B.R.A.N.C.H. is an easy-to-remember acronym for developing with small commits and unit tests.
Letter
Step Name
Step Description
B
Break Down Features
Plan in advance and break down features into smaller, manageable sub-tasks.
R
Review Isolated Changes
Create branches early for each sub-task and ensure each commit has a single responsibility.
A
Add Descriptive Messages
Use clear and descriptive commit messages and follow a consistent commit message format.
N
Nest Tests with Code
Write tests alongside code changes, pairing method implementation with its corresponding test in the same commit.
C
Commit Empty Wrappers
Start with empty files and basic class or function wrappers; use Jest’s test.todo for planned tests.
H
History Clean and Revertible
Maintain a clear and understandable commit history, ensuring commits are functional and verifiable.
By following the B.R.A.N.C.H. framework, you can streamline your development process, making it easier to manage, review, and debug your code.
Certainly! Here's the lesson incorporating your suggestions:
Using Git Bisect to Identify Problematic Commits
Introduction to Git Bisect
Git Bisect is a powerful tool that allows developers to efficiently locate the commit that introduced a bug or issue in the codebase. By performing a binary search through the commit history, Git Bisect can quickly narrow down the problematic commit, saving time and effort compared to manually inspecting each commit.
Benefits of Using Git Bisect
Efficient Bug Identification:
Git Bisect automates the process of finding the commit that introduced a bug, making it faster and more accurate than manual inspection.
Works with Buildable Tested Commits:
When each commit is small, buildable, and tested, Git Bisect can easily isolate the faulty commit without additional debugging.
Saves Time:
The binary search approach significantly reduces the number of commits to check, making the process quicker.
How Git Bisect Works
Git Bisect operates using a binary search algorithm. It requires you to mark one commit as "good" (a commit where the code worked correctly) and one commit as "bad" (a commit where the bug is present). Git then checks out a commit halfway between these two points, and you determine whether this commit is "good" or "bad." This process continues until Git Bisect identifies the exact commit that introduced the issue.
Steps to Use Git Bisect
1. Start the Bisect Session
Begin by telling Git you want to start a bisect session:
git bisect start
2. Mark the Bad Commit
Identify and mark the commit where the bug is present:
git bisect bad <bad_commit_hash>
3. Mark the Good Commit
Identify and mark the last known good commit:
git bisect good <good_commit_hash>
4. Run Tests in Watch Mode
In a separate terminal, run your tests continuously to quickly determine the state of each checked-out commit:
npm run test:watch
5. Bisecting
Git will check out a commit halfway between the good and bad commits. Determine whether this commit is "good" or "bad" based on the test results:
If the tests pass, mark the commit as good:
git bisect good
If the tests fail, mark the commit as bad:
git bisect bad
6. Repeat Until Identification
Repeat the process of marking commits as good or bad based on test results. Git will continue to narrow down the range until it identifies the commit that introduced the issue.
7. Finish the Bisect Session
Once Git Bisect identifies the problematic commit, end the bisect session:
git bisect reset
Understanding Binary Search with an Example
Binary search is an efficient algorithm for finding an item from a sorted list of items. It works by repeatedly dividing in half the portion of the list that could contain the item, until you have narrowed down the possible locations to just one.
Example
I have a random number in the range of 1 to 25. You try to determine what's my number in the least number of guesses. Whenever you take a guess, I'll tell you if my number is higher or lower.
The trick is to always pick the number halfway between the possible range. Let's say my number is 5.
0th step: Guess 13 - I'll say lower.
1st step: Guess 6 - I'll say lower.
2nd step: Guess 3 - I'll say higher.
3rd step: Guess 4 - I'll say higher.
4th step: Guess 5 - Correct!
Through this method, the maximum number of steps you need to take to get to the answer is given by RoundDown(Log2(N)). In this case, N is 25, so Log2(25) = 4.64, rounding down gives 4 steps maximum.
Given the Log function, the efficiency of this algorithm increases as N gets larger:
Log2(1000) = 9.97
Log2(10000) = 13.29 (only 13 guesses needed to guess the correct answer!)
Binary Search Trees (BST)
Binary Search Trees work off the same logic. Nodes are arranged such that smaller nodes are always to the left of the current node.
Here's an example BST with 15 nodes, looking for the number 5:
5 < 8: Go left.
5 > 4: Go right.
5 < 6: Go left.
By following the path described above, you can efficiently locate the number 5 in the tree.
Summary: B.R.A.N.C.H. and Git Bisect
Using the B.R.A.N.C.H. framework ensures that each commit is small, buildable, and tested, making it easier to leverage tools like Git Bisect to quickly identify and resolve issues in the codebase. By maintaining a clean commit history and writing tests alongside code changes, developers can efficiently track down bugs and maintain the overall health of the project.
By following these practices, you can streamline your debugging process, saving time and effort while ensuring that your code remains reliable and maintainable.
This lesson introduces Git Bisect, explains its benefits, and provides a clear, step-by-step guide on how to use it, reinforced with an example of binary search and a visual representation of a binary search tree.