Skip to content

Commit f0aed66

Browse files
committed
Import existing articles using --init
1 parent fffbda9 commit f0aed66

File tree

6 files changed

+352
-0
lines changed

6 files changed

+352
-0
lines changed

Diff for: articles/implicit-interfaces/article.json

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"id": 1581054,
3+
"slug": "the-magic-of-interfaces-in-go-5gga",
4+
"title": "The Magic of Interfaces in Go",
5+
"description": "Introduction DRY, or Don't Repeat Yourself, is one of the most well-known software...",
6+
"url": "https://dev.to/calvinmclean/the-magic-of-interfaces-in-go-5gga",
7+
"tags": [
8+
"programming",
9+
"go",
10+
"learning"
11+
]
12+
}

Diff for: articles/implicit-interfaces/article.md

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
## Introduction
2+
3+
DRY, or Don't Repeat Yourself, is one of the most well-known software development principles. Many engineers will go to great lengths to achieve this laudable goal, so a successful programming language must be designed with this in mind. Interfaces are just one of the tools that programming languages implement to enable code reuse.
4+
5+
Although there are countless differences in the way programming languages implement interfaces, they all define a contract for interaction between software components. Differentiating itself from the typical object-oriented implementation, the Go programming language implements interfaces in a way that aims to reduce complexity, encourage composition, and enable flexible use of the types.
6+
7+
## Implicit Interfaces
8+
9+
The most impactful difference between Go’s interfaces and those from object-oriented languages is that Go’s interfaces work by implicit implementation. Rather than requiring a type to explicitly declare that it implements an interface, Go determines that a type satisfies an interface as long as it implements all of the required method signatures. The different types and the interface they implement are only coupled by the code using them, rather than always being linked together.
10+
11+
This difference offers a few key benefits:
12+
- Reduce complexity and coupling by limiting hierarchical inheritance of interfaces
13+
- Simplify types that implement multiple interfaces
14+
- Enables you to add interfaces later on when they become necessary instead of designing entire applications around them
15+
- Interfaces can be created for types in different packages
16+
17+
This last benefit is the most interesting and unique. The ability to define an interface based on external types leads to a few more specific advantages:
18+
- Client code can define how it will use types rather than relying on library code to tell it how types must be used
19+
- Create polymorphic relationships with types that you do not own or cannot change, which allows you to use them more flexibly
20+
- Solve import cycle issues by changing the location of interface definitions
21+
- Create mocks of imported types to make your code more testable
22+
23+
## No Inheritance
24+
25+
By excluding inheritance, Go reduces the complexity that can occur from a deep hierarchical structure. When programs are designed around a base set of classes or interfaces, any simple changes to those base structures requires a significant refactor.
26+
27+
The alternative practice of composition leads to reduced complexity and more readable code. Composition relies on splitting up functionality among different types, and using them together, instead of re-defining the functionality of types through inheritance. Now you are able to re-use these individual components elsewhere, add more functionality with new components, and easily refactor or remove exiting ones.
28+
29+
Instead of being concerned about what type something _is_, your code just needs to known about what that type _can do_, and luckily the interface informs it.
30+
31+
## Use Case: Polymorphism
32+
33+
Polymorphism is perhaps the entire reason behind the existence of interfaces. This common practice is one of the most effective and easy-to-use methods of code reuse. Since an interface defines a strict contract for how types are used, these different types can be used interchangeably; this is polymorphism.
34+
35+
A very common and useful scenario for this is having a flexible storage backend for your program: use Postgres in production, SQLite when running locally, and mocks when testing (or skip the database mocks, but that's a topic for another day).
36+
37+
```go
38+
type StorageClient interface {
39+
GetValue(id string) (string, error)
40+
}
41+
42+
func NewStorageClient(clientType string) (StorageClient, error) {
43+
switch clientType {
44+
case "sqlite":
45+
return sqlite.NewClient()
46+
case "postgres":
47+
return postgres.NewClient()
48+
default:
49+
return nil, fmt.Errorf("invalid client type: %s", clientType)
50+
}
51+
}
52+
```
53+
54+
This implementation allows you to easily use the `StorageClient` interface throughout the program without concern for the data storage layer behind it.
55+
56+
## Use Case: Testing and Mocks
57+
58+
You can take advantage of the implicit nature of interfaces by defining an interface for the functions you use from an external library. For example, you are assigned a task to implement a function that fetches recent rain data, in inches, from a weather data API. The made-up weather provider publishes Go package called `weather`, which provides a `Client` struct with various weather-related methods returning data in metric units:
59+
60+
```go
61+
func GetRainInches(since time.Duration, client weather.Client) (float32, error) {
62+
rainMM, err := client.GetRain(since)
63+
if err != nil {
64+
return 0, fmt.Errorf("error getting data from API: %w", err)
65+
}
66+
67+
return rainMM / 2.54, nil
68+
}
69+
```
70+
71+
How will you unit test this code? Since Go has implicit interfaces, you can create your own interface that just defines the methods that you need from the library. Now, if your function expects this interface instead, you can create your own mocks. Since you currently just need the `GetRain` method, this is really simple:
72+
```go
73+
type WeatherClient interface {
74+
GetRain(time.Duration) (float32, error)
75+
}
76+
77+
func GetRainInches(since time.Duration, client WeatherClient) (float32, error) {
78+
rainMM, err := client.GetRain(since)
79+
if err != nil {
80+
return 0, fmt.Errorf("error getting data from API: %w", err)
81+
}
82+
83+
return rainMM / 2.54, nil
84+
}
85+
```
86+
Then, your test file might contain a new struct that also implements the interface:
87+
```go
88+
type MockWeatherClient struct {
89+
expectedErr error
90+
expectedMM float32
91+
}
92+
93+
func (c MockWeatherClient) GetRain(time.Duration) (float32, error) {
94+
return c.expectedMM, c.expectedErr
95+
}
96+
```
97+
After this simple refactor, you do not depend on external libraries to provide interfaces that make your own code testable! You have the additional side-effect of being one step closer to allowing your program to use different weather APIs generically.
98+
99+
# Conclusion and Warnings
100+
101+
While interfaces in Go were designed with considerations for simplicity and avoiding some of the common pitfalls of object-oriented patterns, there are still some things to be aware of.
102+
103+
It may be tempting to define interfaces for everything with the hopes that you can create a more generic and flexible program. Remember that one of implicit interfaces is that you can easily create a new interface when you need it without making changes to any existing types, so there is no benefit to creating interfaces early in the process. Additionally, since types do not explicitly declare which interfaces they implement, it may be hard to tell which types are actually being used by your program when you have superfluous interfaces.
104+
105+
While there are always tradeoffs and no perfect solutions, I have found Go's version of interfaces to be incredibly flexible, intuitive, and useful. I have been able to create more flexible programs and improve testability all while minimizing complexity.

Diff for: articles/note-taking/article.json

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"id": 1545813,
3+
"slug": "note-worthy-productivity-scribble-your-way-to-success-4e64",
4+
"title": "Taking Notes to Improve Your Productivity",
5+
"description": "Photo by David Travis on Unsplash Introduction Taking notes is like creating a swap...",
6+
"url": "https://dev.to/calvinmclean/note-worthy-productivity-scribble-your-way-to-success-4e64",
7+
"tags": [
8+
"productivity"
9+
]
10+
}

Diff for: articles/note-taking/article.md

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<sup>Photo by <a href="https://unsplash.com/@dtravisphd?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">David Travis</a> on <a href="https://unsplash.com/photos/5bYxXawHOQg?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a></sup>
2+
3+
## Introduction
4+
5+
Taking notes is like creating a swap partition for your brain. This extension of your random-access memory enables you to offload lower-priority information. This low-effort habit can decrease your mental load and increase productivity.
6+
7+
Learn to take notes effectively by emphasizing simplicity and integrating the habit into your existing workflow. You will realize the benefits in your day-to-day work all while creating a record of your knowledge and achievements.
8+
9+
10+
## Not just for school
11+
12+
I'm sure we are all familiar with taking notes from school. In my experience, this was a tedious requirement with only a few actual benefits:
13+
1. The information prioritized by the teacher in class is more likely to be the subject of a test question, so it's good to keep track of these topics
14+
2. Writing things down can help commit it to memory
15+
3. Looking busy in class will reduce your chances of getting called on by the teacher
16+
17+
Some of these motivations are irrelevant in a work environment, but note-taking remains a valuable skill that can serve the following purposes:
18+
- Creates a reference that is specific to your work experience. In a career where you are constantly learning new things, this is a huge asset
19+
- Helps reduce the mental load of context-switching when going to meetings, switching between codebases, and getting interrupted by urgent bugs
20+
- Helps you organize and manage the flood of new information when starting a new job
21+
22+
To be more specific, taking notes allows you to keep track of:
23+
- Links for hard-to-find documentation or guides that you might frequently reference
24+
- Action items from a meeting
25+
- Complicated commands that you might need to use again
26+
- Steps of a complex or tedious process
27+
- Interesting code snippets
28+
- TODO items or small bugs that aren't significant enough to go into an issue tracker
29+
- Concrete achievements and topics for annual performance reviews
30+
- Different solutions you have attempted when solving a complex problem
31+
32+
In addition to recording information, your notes become a place where you can let your thoughts flow. You can have a one-sided conversation in your notes, similar to [Rubber duck debugging](https://en.wikipedia.org/wiki/Rubber_duck_debugging), but with the ability to re-read your thoughts and ideas.
33+
34+
35+
## Organizing
36+
37+
I encourage you to get started taking notes today. Don’t worry about organization until it becomes a necessity. This will allow you to form the habit of low effort note-taking in a way that feels natural to you. Later, you can begin to introduce more complexity to the process without interrupting your flow.
38+
39+
Once you get serious about taking notes, you will need a way to organize them. My preferred method is to organize by time. I create a filesystem structure around the year and month with filenames based on the week, so it ends up looking like this:
40+
```shell
41+
2023
42+
└── 07Jul
43+
├── Week_of_the_17th.md
44+
├── Week_of_the_24th.md
45+
└── Week_of_the_31st.md
46+
```
47+
48+
One of these files might look like:
49+
```md
50+
# Week of the 31st (July 2023)
51+
52+
### Monday 31
53+
54+
55+
### Tuesday 01 (August 2023)
56+
57+
58+
### Wednesday 02 (August 2023)
59+
60+
61+
### Thursday 03 (August 2023)
62+
63+
64+
### Friday 04 (August 2023)
65+
66+
```
67+
68+
There are numerous benefits to organizing by date rather than topic:
69+
- You don't need to find the correct place for every topic, just put it in this week's note; as long as you use consistent terminology, you can later retrieve notes relevant to a particular topic by searching
70+
- You can easily review what you were working on last week when you come back from the weekend
71+
- You can find concrete examples of what you achieved in a time period for annual/quarterly reviews (or new job interviews)
72+
- It reminds you what your thought process was when you created a specific PR or commit
73+
- The directory structure creates a place to save ad hoc files that you use for the week, like log files for bugs and drafts of documentation
74+
75+
The most important consideration when organizing and creating notes is simplicity. The purpose of the notes is to improve your productivity and create a smooth process for yourself. In order to further streamline my process, I created a small CLI program that can be used to automate the building of this file structure and templating out a new week's note: [`gnotes`](https://github.com/calvinmclean/gnotes).
76+
77+
Every Monday, I start my day by running the `gnotes` command. It will intelligently create the directory structure for the year and month, then create the base note file for this week. Even if you run the command on a day other than Monday, it will start the week with Monday to keep things consistent.
78+
79+
Writing notes in a Markdown format is also very beneficial because it allows you to have nice formatting, but still keeps a simple plaintext file that is easily searchable with `grep` or other text search tools.
80+
81+
## Conclusion
82+
83+
Now that you are familiar with the benefits of note-taking, I encourage you to give it a try. Just remember to keep it simple and find what works for you!

Diff for: articles/taskfile/article.json

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"id": 1532922,
3+
"slug": "stop-using-makefile-use-taskfile-instead-4hm9",
4+
"title": "Stop Using Makefile (Use Taskfile Instead)",
5+
"description": "Introduction GNU Make originated as a dependency-tracking build tool that automates the...",
6+
"url": "https://dev.to/calvinmclean/stop-using-makefile-use-taskfile-instead-4hm9",
7+
"tags": [
8+
"tooling",
9+
"learning",
10+
"productivity",
11+
"go"
12+
]
13+
}

Diff for: articles/taskfile/article.md

+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
### Introduction
2+
3+
GNU Make originated as a dependency-tracking build tool that automates the build process of source files and libraries. Although many modern languages provide built-in dependency management and build tools, a Makefile still finds its place in these projects. In these cases, Makefiles are used as a collection of aliases for actions like test, build, and lint. In this article, I will show you that there is a better option for these use-cases: [`task`](https://taskfile.dev).
4+
5+
### But Make is standard practice!
6+
7+
Yes, Make has been continuously developed over nearly 50 years, leading to a robust set of features and a rich history in software engineering. It will likely be around forever, but these deep roots are the same thing preventing it from being user-friendly and intuitive.
8+
9+
- Makefiles often become a mess with lots of environment variables, macros, and special symbols
10+
- You MUST use tabs (ugh)
11+
- You can't pass arguments without using environment variables
12+
- An annoying convention in Makefiles is to string together a lot of environment variables to construct commands
13+
- `.PHONY` is required when using non-file targets as command aliases
14+
- Many features are designed around building files, which often isn't relevant in these modern scenarios
15+
16+
When we aren't using it for the core features Make is built and designed around, we have to ask ourselves if there is an alternative.
17+
18+
### Introduction to Task
19+
20+
This is where the Taskfile comes in. The YAML format creates a self-documenting file that states how each "task" will behave.
21+
The verbose format is a welcome feature in comparison to the Makefile and will result in a more approachable file. When integrated in an open-source project, it can make it easier for new contributors to get started.
22+
23+
After following the [simple install process](https://taskfile.dev/installation/), you just need to run `task --init` to create a simple example file. This example shows how to use environment variables and execute commands.
24+
25+
```yml
26+
version: '3'
27+
28+
vars:
29+
GREETING: Hello, World!
30+
31+
tasks:
32+
default:
33+
cmds:
34+
- echo "{{.GREETING}}"
35+
silent: true
36+
```
37+
38+
At this point, you have all the information needed to cover the basic use-cases. Rather than providing a tutorial here, I encourage you to check out the [official documentation](https://taskfile.dev/usage/). The well-written guide starts by showing the basics with concrete examples and graduates into more complex functionality as you scroll. This is a welcome sight in comparison to the large plain HTML page provided by Make.
39+
40+
### Rewriting a Makefile to Taskfile
41+
42+
I recently read the [Guide to using Makefile with Go](https://levelup.gitconnected.com/a-comprehensive-guide-for-using-makefile-in-golang-projects-c89edebcbe6e) by Ramseyjiang. This is an interesting read and makes good points about providing a consistent interface for common tasks and builds. It left me thinking about how the developer experience could be further improved by using a Taskfile instead.
43+
44+
This is the example Makefile created by the article's author:
45+
```Makefile
46+
APP_NAME = myapp
47+
GO_FILES = $(wildcard *.go)
48+
GO_CMD = go
49+
GO_BUILD = $(GO_CMD) build
50+
GO_TEST = $(GO_CMD) test
51+
52+
all: build
53+
54+
executable
55+
build: $(APP_NAME)
56+
57+
$(APP_NAME): $(GO_FILES)
58+
$(GO_BUILD) -o $(APP_NAME) $(GO_FILES)
59+
60+
test:
61+
$(GO_TEST) -v ./... -cover
62+
63+
.PHONY: all build test
64+
```
65+
66+
Here is my translation to Taskfile:
67+
```yml
68+
version: "3"
69+
70+
vars:
71+
APP_NAME: myapp
72+
73+
tasks:
74+
default:
75+
cmds:
76+
- task: build
77+
78+
build:
79+
cmds:
80+
- go build -o {{.APP_NAME}} *.go
81+
sources:
82+
- "*.go"
83+
generates:
84+
- "{{.APP_NAME}}"
85+
86+
test:
87+
cmds:
88+
- go test -v ./... -cover
89+
```
90+
91+
If you're counting lines, you might notice the Taskfile has a few more. This could be equalized by using `cmd` with a string instead of `cmds` and the array, but my priority here is to create something easy to read and build upon.
92+
93+
### Task Features
94+
95+
Task covers all of the main features of Make:
96+
97+
- Intelligent tracking of input/output files to skip unnecessary builds
98+
- Dependencies between tasks or targets
99+
- Include other files
100+
- Access environment variables
101+
102+
In addition, here are a few of my favorite Task features:
103+
104+
- Automatic CLI usage output and autocompletion
105+
- Run multiple tasks in parallel. I use this one to start a Go backend service and `npm run dev` for the frontend with a single command
106+
- Control of output syntax which is useful for grouping output in CI environments
107+
- Forward CLI arguments to task commands. I use this to run `task up -d` which will start Docker containers in detached mode
108+
- Global Taskfile: `task` will walk up the filesystem tree until it finds a Taskfile, or use `-g`/`--global` to search your home directory
109+
110+
I like to add a `docs` task to my global Taskfile that will open the usage guide in my default browser:
111+
112+
```yml
113+
version: '3'
114+
115+
tasks:
116+
docs:
117+
cmd: open https://taskfile.dev/usage/
118+
```
119+
120+
### Conclusion
121+
122+
Although I am arguing here that a Taskfile is better than a Makefile for many modern projects, I am not saying that Taskfile is a replacement for Makefile in all cases. The two tools have a lot of overlap, but are ultimately designed for different use-cases. There will always be a place for Makefiles, but your modern project is probably better off with a Taskfile. I hope your next step from here is the [installation docs](https://taskfile.dev/installation/) for Task so you can give it a try!
123+
124+
### Links
125+
- Photo by <a href="https://unsplash.com/@ffstop?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Fotis Fotopoulos</a> on <a href="https://unsplash.com/photos/DuHKoV44prg?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>
126+
- [Task](https://taskfile.dev)
127+
- [Guide to using Makefile with Go](https://levelup.gitconnected.com/a-comprehensive-guide-for-using-makefile-in-golang-projects-c89edebcbe6e)
128+
- [GNU Make](https://www.gnu.org/software/make/)
129+
- ["Simple Makefile"](https://www.gnu.org/software/make/manual/html_node/Simple-Makefile.html)

0 commit comments

Comments
 (0)