On Fri, Jun 1, 2012 at 5:42 PM, Kyle Lemons <
kev...@google.com> wrote:
> No, I insist that the 3 lines in question are always better than the 1 line
> in question, because it's more readable, clearer, more maintainable, etc.
> You will feel the pain later. YAGNI, which is dubious outside of XP
> anyway, does not apply.
What about DRY (don't repeat yourself)? Does that apply? One can
debate ad nauseum about _where_ a particular error should be handled,
but the fact remains that there is only _one_ place a particular error
should be handled in a particular program. If programmers must
explicitly test for errors on every call, then they will be repeating
the same logic at 90% of their call sites. I totally see the value of
returning error values at an API boundary, and I am not arguing with
that. I just think that explicitly handling error values on every
single function call is silly.
Consider the two programs below (runnable code here:
http://play.golang.org/p/6YxbRyMxzc). Does anyone actually believe
that the first is more readable and maintainable than the second one?
The latter program is still benefiting from Go's API design because
all calls that may generate an error must be wrapped in a Must/MustIO,
so it is visible where potential errors are being propagated upward.
- Aaron
(Disclaimer: I know the programs below repeat themselves -- but only
for the purpose of keeping the example as simple as possible while
illustrating what happens in the case of nested function calls.)
////////////////////////////////////////////////////////////////////////////////
// Program A: 64 lines
////////////////////////////////////////////////////////////////////////////////
func reportA(n int) error {
_, err := fmt.Printf("read %d bytes\n", n)
if err != nil {
return err
}
return nil
}
func fA(r io.Reader) (string, error) {
buf := make([]byte, 4)
n, err := r.Read(buf)
if err != nil {
return "", err
}
err = reportA(n)
if err != nil {
return "", err
}
s, err := gA(r)
if err != nil {
return "", err
}
return rot1(buf) + s, nil
}
func gA(r io.Reader) (string, error) {
buf := make([]byte, 4)
n, err := r.Read(buf)
if err != nil {
return "", err
}
err = reportA(n)
if err != nil {
return "", err
}
s, err := hA(r)
if err != nil {
return "", err
}
return rot1(buf) + s, nil
}
func hA(r io.Reader) (string, error) {
buf := make([]byte, 4)
n, err := r.Read(buf)
if err != nil {
return "", err
}
err = reportA(n)
if err != nil {
return "", err
}
return rot1(buf), nil
}
func mainA() {
reader := strings.NewReader("Hello, world")
s, err := fA(reader)
if err != nil {
fmt.Println("there was a problem:", err)
} else {
fmt.Println("result:", s)
}
}
////////////////////////////////////////////////////////////////////////////////
// Program B: 35 lines
////////////////////////////////////////////////////////////////////////////////
func reportB(n int) {
MustIO(fmt.Printf("read %d bytes\n", n))
}
func fB(r io.Reader) string {
buf := make([]byte, 4)
reportB(MustIO(r.Read(buf)))
return rot1(buf) + gB(r)
}
func gB(r io.Reader) string {
buf := make([]byte, 4)
reportB(MustIO(r.Read(buf)))
return rot1(buf) + hB(r)
}
func hB(r io.Reader) string {
buf := make([]byte, 4)
reportB(MustIO(r.Read(buf)))
return rot1(buf)
}
func mainB() {
reader := strings.NewReader("Hello, world")
s, err :=
func () (s string, err error) {
defer SetError(&err)
return fB(reader), nil
}()
if err != nil {
fmt.Println("there was a problem:", err)
} else {
fmt.Println("result:", s)
}
}