Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Increase test coverage #3

Merged
merged 6 commits into from
Jul 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 93 additions & 3 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,96 @@ if it has a more-specific type than it actually needs.
For example,
if your function takes a `*os.File` parameter,
but it’s only ever used for its `Read` method,
it could be specified as an abstract `io.Reader` instead,
making it possible to use the function
on a much wider variety of concrete types.
it could be specified as an abstract `io.Reader` instead

## Why decouple?

When you decouple a function parameter from its too-specific type,
you broaden the set of values on which it can operate.

You also make it easier to test.
For a simple example,
suppose you’re testing this function:

```go
func CountLines(f *os.File) (int, error) {
var result int
sc := bufio.NewScanner(f)
for sc.Scan() {
result++
}
return result, sc.Err()
}
```

Your unit test will need to open a testdata file and pass it to this function to get a result.
But as `decouple` can tell you,
`f` is only ever used as an `io.Reader`
(the type of the argument to [bufio.NewScanner](https://pkg.go.dev/bufio#NewScanner)).

If you were testing `func CountLines(r io.Reader) (int, error)` instead,
the unit test can simply pass it something like `strings.NewReader("a\nb\nc")`.

## Installation

```sh
go install github.com/bobg/decouple/cmd/decouple@latest
```

## Usage

```sh
decouple [-v] [DIR]
```

This produces a report about the Go packages rooted at DIR
(the current directory by default).
With -v,
very verbose debugging output is printed along the way.

The report will be empty if decouple has no findings.
Otherwise, a report will look something like this:

```
$ decouple
/home/bobg/kodigcs/handle.go:105:18: handleDir
req: [Context]
w: io.Writer
/home/bobg/kodigcs/handle.go:167:18: handleNFO
req: [Context]
w: [Header Write]
/home/bobg/kodigcs/handle.go:428:6: isStale
t: [Before]
/home/bobg/kodigcs/imdb.go:59:6: parseIMDbPage
cl: [Do]
```

This is the output when running decouple on [the current commit](https://github.com/bobg/kodigcs/commit/f4e8cf0e44de0ea98fa7ad4f88705324ff446444)
of [kodigcs](https://github.com/bobg/kodigcs).
It’s saying that:

- In the function [handleDir](https://github.com/bobg/kodigcs/blob/f4e8cf0e44de0ea98fa7ad4f88705324ff446444/handle.go#L105),
The `req` parameter is being used only for its `Context` method
and so could be declared as `interface{ Context() context.Context }`,
allowing objects other than `*http.Request` values to be passed in here;
- Also in [handleDir](https://github.com/bobg/kodigcs/blob/f4e8cf0e44de0ea98fa7ad4f88705324ff446444/handle.go#L105),
`w` could be an `io.Writer`,
allowing more types to be used than just `http.ResponseWriter`;
- Similarly in [handleNFO](https://github.com/bobg/kodigcs/blob/f4e8cf0e44de0ea98fa7ad4f88705324ff446444/handle.go#L167),
`req` is used only for its `Context` method,
and `w` for its `Write` and `Header` methods
(more than `io.Writer`, but less than `http.ResponseWriter`);
- Anything with a `Before(time.Time) bool` method
could be used in [isStale](https://github.com/bobg/kodigcs/blob/f4e8cf0e44de0ea98fa7ad4f88705324ff446444/handle.go#L428),
it does not need to be limited to `time.Time`;
- The `*http.Client` argument of [parseIMDbPage](https://github.com/bobg/kodigcs/blob/f4e8cf0e44de0ea98fa7ad4f88705324ff446444/imdb.go#L59)
is being used only for its `Do` method.

Note that,
in the report,
the presence of square brackets means “this is a set of methods,”
while the absence of them means “this is an existing type that already has the right method set”
(as in the `io.Writer` line in the example above).
Decouple can’t always find a suitable existing type even when one exists,
and if two or more types match,
it doesn’t always choose the best one.
Loading