Language proposal: labelled "with" statements to help make test code easier to write

561 views
Skip to first unread message

Warren Stephens

unread,
Feb 29, 2020, 1:11:53 PM2/29/20
to golang-nuts
I often write a function or module to handle some process that takes 3 or 4 steps to complete.

After I am happy with the code I then proceed to write tests for the code, 
but find that I am compelled to chop the code into pieces in order to simplify the test code 
-- thereby losing the nice top down readability with which I started.

The influence that test code has on the structure of the application code is highly undesirable.

Test code should not exert so much influence over how the primary code is written.

Here is a proposal that could eliminate this influence -- see explanation for with statements at the end.


package main

import (
"bufio"
"fmt"
"os"
"strings"
)

func main() {

fmt.Printf("Hello folks!\n")
result, err := doSomeStuff("filename.txt", "find_these", 10)
if err != nil {
fmt.Printf("doThisStuff did not succeed! %v\n", err)
}
fmt.Printf("result length is: %v\n", len(result))
}

func doSomeStuff(thing1 string, thing2 string, thing3 int) (map[string]int, error) {

mystep1 with: thing1 // <---------- hide all function parameters except for thing1

file, err := os.Open(thing1)
if err != nil {
return nil, err
}
defer file.Close()

var lines []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
lines = append(lines, scanner.Text())
}
err = scanner.Err()
if err != nil {
return nil, err
}

mystep2 with: lines, thing2  // <---------- hide all local variables from above except for lines and thing2

mymap := make(map[string]int)
for _, line := range lines {
if strings.Contains(line, thing2) {
count := strings.Count(line, thing2)
mymap[line] = count
}
}

mystep3 with: mymap, thing3 // <---------- hide all local variables from above except for mymap and thing3
for k, v := range mymap {
if v > thing3 {
mymap[k] = 0
}
}

return mymap, nil
}

The labelled with statements would allow for a test to be written specifically for each step of a function.  

Each test would begin at the with statement, providing the variables and values required, and end at the next with statement or return statement.

Each step of a function could be tested (or not) without having to refactor the function into testable pieces
which would lose the natural structure of the original code.

Mhd Shulhan

unread,
Feb 29, 2020, 3:36:34 PM2/29/20
to Warren Stephens, golang-nuts


Pada tanggal Min, 1 Mar 2020 01.11, Warren Stephens <wste...@prognoshealth.com> menulis:
I often write a function or module to handle some process that takes 3 or 4 steps to complete.

After I am happy with the code I then proceed to write tests for the code, 
but find that I am compelled to chop the code into pieces in order to simplify the test code 
-- thereby losing the nice top down readability with which I started.

The influence that test code has on the structure of the application code is highly undesirable.

Test code should not exert so much influence over how the primary code is written.


In my experiences, it is a good things that the test code has influence to the structure of application code, because if the code can not be easily tested, there is something wrong with how I solve the problem, or maybe I need to break it down into smaller unit and test only some parts of unit.


The labelled with statements would allow for a test to be written specifically for each step of a function.  

Each test would begin at the with statement, providing the variables and values required, and end at the next with statement or return statement.

Now the application code are mixed with the test functionality. This is not good design. Testing and application code are different domain, even if its in the same repository.

Dan Kortschak

unread,
Feb 29, 2020, 4:07:48 PM2/29/20
to Warren Stephens, golang-nuts
Why can't you spell "with" as "func"?
> --
> You received this message because you are subscribed to the Google
> Groups "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it,
> send an email to golang-nuts...@googlegroups.com.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/golang-nuts/8eb18eb2-69e7-404a-86be-6c7893a9a7c1%40googlegroups.com
> .


Jason E. Aten

unread,
Mar 1, 2020, 2:29:26 AM3/1/20
to golang-nuts
Try Test Driven Development. Since testing is non-negotiable, write your tests first and let them structure your implementation incrementally.

It will radically improve your software.

Warren Stephens

unread,
Mar 1, 2020, 9:16:57 AM3/1/20
to golang-nuts
All,

Thanks for the advice, but I should have mentioned that I already know about Test Driven Development.

I live in the real world of commercial software development (>3 decades).  Here is the real world scenario:
  1. Write some code quick — must stay on schedule, there is actually no time to write tests as all, others are waiting (i.e. no tests)
  2. Deploy the code, and manually check that it is doing its job (i.e. no tests)
  3. Move on to something else more timely or important (i.e. no tests)
  4. Times passes, the code is working (i.e. still no tests)
Eventually someone comes along to write tests.  And writing tests in Go is much easier if the code is broken up into “testable” pieces.

So the developer is in the situation of refactoring code that is known to be working in order to write tests! — which are rarely going to be comprehensive anyway.  The risk is breaking something that isn’t broken!

With my language proposal above, adding the with statements does not modifying the behavior of the code at all — it should literally compile exactly the same way.  Its main benefit is to enable tests to be more easily written without restructuring the existing code.

In my opinion, it would be nice if a developer would never have to restructure code in order to write tests.  Tests are quite often written well after the code is in production.  That is the real world scenario.

Imagine that you are doing a code review on a pull request, and the only thing that has been changed is to add with statements and tests -- Zero risk!  Approved!  Done!

Ian Lance Taylor

unread,
Mar 1, 2020, 10:39:49 AM3/1/20
to Warren Stephens, golang-nuts
On Sat, Feb 29, 2020 at 10:11 AM Warren Stephens
<wste...@prognoshealth.com> wrote:
>
> The labelled with statements would allow for a test to be written specifically for each step of a function.
>
> Each test would begin at the with statement, providing the variables and values required, and end at the next with statement or return statement.
>
> Each step of a function could be tested (or not) without having to refactor the function into testable pieces
> which would lose the natural structure of the original code.

I feel like there is something missing here. How do you write a test
that uses a with statement?

Ian

Warren Stephens

unread,
Mar 1, 2020, 11:20:41 AM3/1/20
to golang-nuts
// I don't want to get into the specific syntax of the test code directly,    
// but a test would start execution here, supplying "lines" and "thing2"
//
   
mystep2 with: lines, thing2  // <---------- hide all local variables from above except for lines and thing2

    mymap := make(map[string]int)
   for _, line := range lines {
           if strings.Contains(line, thing2) {
                   count := strings.Count(line, thing2)
                   mymap[line] = count
            }
   }

// and the test would end here -- because the next line is another "with" (or return) statement
// it would be able to do Asserts and such on the variables within the scope of this code segment

Ian,

An alternative approach would be to have:

   mystep2 with: lines, thing2 => mymap

to specify all of the intermediate variables of interest for the test.

These testable code chunks, delimited by with statements, act like hidden funcs.  The actual call could be something like test_doSomeStuff@mystep2

Warren

Robert Engels

unread,
Mar 1, 2020, 1:22:57 PM3/1/20
to Warren Stephens, golang-nuts
If you need to test your code that way it needs a better design. If the internal code within the function under test is trivial then testing purely the outer function will suffice. If the internal code is that complex it should be its own non-exported function that can be documented and tested in isolation. It will pay dividends to future maintainers. 

On Mar 1, 2020, at 10:21 AM, Warren Stephens <wste...@prognoshealth.com> wrote:


--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.

Warren Stephens

unread,
Mar 1, 2020, 2:58:07 PM3/1/20
to golang-nuts
Robert,

To me, code broken up into smaller pieces can “seem” more maintainable, but (actually more importantly to me) often is far less comprehensible.  And I must comprehend it in order to maintain it.

I recently returned to a piece of code that someone else had refactored in order to write tests.  I found that I no longer understood my own code.  Where does the such-and-such get done now???  This calls that, and that calls that, and so forth.  Like a Russian Doll.  I could have used a diagram.  And any errors have to bubble up out of that stuff.  Thankfully better error wrapping has been added to Go.

I guess everyone doesn’t think the same way.  I am very top-down sequence oriented.  I don’t like to jump around in code in order to figure out what is going on.

Also, I believe that with statements could provide two additional benefits (in addition to making tests easier to write without refactoring):

1) Allow someone to read source code from top to bottom, paying most attention to the with statements, and come away with a quick understanding of the code.

2) Correct the problem that Go has with the common “err” variable.  Err could be redeclared in each section of with code — enforcing that each section is dealing with its own “err” values.  Err would not drop thru unless specifically listed.

Warren

Ian Lance Taylor

unread,
Mar 1, 2020, 3:44:17 PM3/1/20
to Warren Stephens, golang-nuts
On Sun, Mar 1, 2020 at 8:21 AM Warren Stephens
<wste...@prognoshealth.com> wrote:
>
> // I don't want to get into the specific syntax of the test code directly,
> // but a test would start execution here, supplying "lines" and "thing2"
> //
> mystep2 with: lines, thing2 // <---------- hide all local variables from above except for lines and thing2
>
> mymap := make(map[string]int)
> for _, line := range lines {
> if strings.Contains(line, thing2) {
> count := strings.Count(line, thing2)
> mymap[line] = count
> }
> }
>
> // and the test would end here -- because the next line is another "with" (or return) statement
> // it would be able to do Asserts and such on the variables within the scope of this code segment
>
> Ian,
>
> An alternative approach would be to have:
>
> mystep2 with: lines, thing2 => mymap
>
> to specify all of the intermediate variables of interest for the test.
>
> These testable code chunks, delimited by with statements, act like hidden funcs. The actual call could be something like test_doSomeStuff@mystep2

To me this all seems very unlike anything else in the Go language.
There is nothing syntactically similar to this anywhere else. I think
it is extremely unlikely that Go would adopt this idea.

Ian

Warren Stephens

unread,
Mar 1, 2020, 4:06:12 PM3/1/20
to golang-nuts
No worries.  It could made to look more Go-like...

   internal mystep2(lines, thing2): (mymap, err)

or something like that.  Where "internal" is like "func".  The colon is already used for labels.  And the results are like the values supplied after a return. 

Warren Stephens

unread,
Mar 1, 2020, 4:11:33 PM3/1/20
to golang-nuts
Also note that these internal pieces of code would eliminate the inefficiency of calls -- which is non-trivial as I am sometimes dealing with big-data of billions of records of data.

Axel Wagner

unread,
Mar 1, 2020, 5:14:01 PM3/1/20
to Warren Stephens, golang-nuts
On Sun, Mar 1, 2020 at 3:17 PM Warren Stephens <wste...@prognoshealth.com> wrote:
So the developer is in the situation of refactoring code that is known to be working in order to write tests! — which are rarely going to be comprehensive anyway.  The risk is breaking something that isn’t broken! 

With my language proposal above, adding the with statements does not modifying the behavior of the code at all — it should literally compile exactly the same way.

This seems to contradict how you described the suggestion above. Specifically, you wrote

mystep2 with: lines, thing2 // <---------- hide all local variables from above except for lines and thing2

I think this actually does have the potential to change the behavior of code. For example, consider this code:

a, e := F()
x := &e
//with a:
b, e := G()
y := &e
fmt.Println(x == y)

Inserting the `with` statement, AIUI, would change the output of this program from "true", to "false". Because before the change, the short assignment did not declare a new `e`, but after the change, `e` is made invisible to the code after, so a new variable (with a different address) is declared. Now, this is a toy example. But note that this particular semantic change is very subtle and unlikely to be caught in code review (especially if, as you suggest, people start just approving pull requests that only add `with` statements). And while I personally don't often have problems with the short assignment operator (not) shadowing things, note that it is a very common pitfall mentioned by newcomers to the language.

The reason I'm mentioning it, is that this property *also* means that the transformation between `with` and destructuring into `func`s is very straight-forward. So, if you are fine with this problem, I really don't understand why you are afraid that splitting one function into three would somehow break - it does exactly the same thing, after all.
 
  Its main benefit is to enable tests to be more easily written without restructuring the existing code.

In my opinion, it would be nice if a developer would never have to restructure code in order to write tests.  Tests are quite often written well after the code is in production.  That is the real world scenario.

Imagine that you are doing a code review on a pull request, and the only thing that has been changed is to add with statements and tests -- Zero risk!  Approved!  Done!

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.

Axel Wagner

unread,
Mar 1, 2020, 5:15:45 PM3/1/20
to Warren Stephens, golang-nuts
(uhm, nevermind that I didn't actually internalize the supposed syntax enough. Should be "step with: a", I guess?)

Warren Stephens

unread,
Mar 1, 2020, 5:35:18 PM3/1/20
to golang-nuts
Axel,

I like your example.  Var e would get re-declared -- so that is a change.  However, var x is not visible past the with statement -- only var a is brought forward.

So we need another attempt at breaking this.  :-) 

I will try to think of how to get a re-declaration to break something, providing that no other lines of code change.

Splitting the code into separate funcs introduces calls, and I work with big data, many billions of records.  So that introduces production inefficiency for the purpose of test writing -- which costs time and money in the cloud.

Warren

Nigel Tao

unread,
Mar 1, 2020, 5:46:26 PM3/1/20
to Warren Stephens, golang-nuts
On Mon, Mar 2, 2020 at 8:06 AM Warren Stephens
<wste...@prognoshealth.com> wrote:
> It could made to look more Go-like...
>
> internal mystep2(lines, thing2): (mymap, err)

Ian isn't talking about the particular syntax (e.g. punctuation or
keywords). He is talking about the semantics (statements that hide
scope, hidden funcs) being unlike anything else in Go.

Making a language change is a big deal, especially if there's plenty
of existing code out there that uses "with" as a variable name, and
the fact that not everyone upgrades to version N+1 at the same time.

But you don't need a language change (or to wait e.g. 6 months for the
toolchain release a new stable version). See
https://blog.golang.org/generate and
https://play.golang.org/p/vIXRLTnGlRf

Axel Wagner

unread,
Mar 1, 2020, 5:48:31 PM3/1/20
to Warren Stephens, golang-nuts
On Sun, Mar 1, 2020 at 11:35 PM Warren Stephens <wste...@prognoshealth.com> wrote:
Axel,

I like your example.  Var e would get re-declared -- so that is a change.  However, var x is not visible past the with statement -- only var a is brought forward.

So we need another attempt at breaking this.  :-) 

Careless of me. Add `x` to the `with` statement.
 
Splitting the code into separate funcs introduces calls, and I work with big data, many billions of records.  So that introduces production inefficiency for the purpose of test writing -- which costs time and money in the cloud.

Note that you are very much not alone with this. For example, I have worked on systems with trillions of records. Many on this list - and in particular, on the Go team - are in very similar boats. I don't say this to brag - but just to clarify, that a lack of experience with scale is not generally what informs opinions here.

I don't really think your suggestion is a priori more efficient, FWIW. There is no reason the compiler actually *has* to insert function calls, and given that your suggestion is semantically equivalent to splitting the code into multiple functions (and that is the most likely way it would be implemented - after all, with the GC, defer and other features, you can't just jmp into the middle of a function anyway), I feel it's hard to justify why it would be more efficient without actually looking at a finished implementation (and weighing that against putting that effort into better compiler-optimizations).

In any case - I don't think efficiency can or should really drive the discussion.
 

Warren

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.

Robert Engels

unread,
Mar 1, 2020, 6:02:41 PM3/1/20
to Axel Wagner, Warren Stephens, golang-nuts
Any decent compiler should in-line these private function calls in all cases - so no function call overhead. 

Give me a system with lots of small easily tested and documented methods over one with 1000 line methods any day. 

On Mar 1, 2020, at 4:48 PM, 'Axel Wagner' via golang-nuts <golan...@googlegroups.com> wrote:



Axel Wagner

unread,
Mar 1, 2020, 6:14:03 PM3/1/20
to Robert Engels, Warren Stephens, golang-nuts
On Mon, Mar 2, 2020 at 12:01 AM Robert Engels <ren...@ix.netcom.com> wrote:
Any decent compiler should in-line these private function calls in all cases - so no function call overhead. 

FTR, while I agree that any compiler *can* inline them, I wouldn't feel confident to make as broad an assertion as this - unless I was also prepared to admit that gc does not qualify as a "decent" compiler :) Inlining decisions aren't trivial and I can very well imagine that the way the heuristic is coded today, it wouldn't inline here (in particular, if the individual steps are large). That's why I said that IMO the cost of implementing this change should be weighed against the cost of improving that heuristic, FWIW. If runtime efficiency was the only reason to do the change. I don't believe it would come out ahead in that comparison, but we should still be willing to be surprised there :)

John Arundel

unread,
Mar 2, 2020, 6:33:46 AM3/2/20
to golang-nuts
On Saturday, 29 February 2020 18:11:53 UTC, Warren Stephens wrote:
The influence that test code has on the structure of the application code is highly undesirable. 

On the contrary, I think designing for testability improves your architecture considerably! And if you develop test-first, you never write untestable functions.

Brian Candler

unread,
Mar 2, 2020, 6:47:01 AM3/2/20
to golang-nuts
Tests that depend on the internal implementation of a function are very brittle: any refactoring and they're likely to break, which defeats much of the benefit.

You can check that a function has the required visible behaviour without hooking directly into its internals, by comparing the state of the system before and after the function call.  Or you can check for side effects as they happen by passing in mock objects - even then, too much of this can make tests brittle.

Removing function calls in the name of efficiency is a case of premature optimisation.  Unless you have profiling which shows that this code is on the hot path in your application, it won't make any difference.

Warren Stephens

unread,
Mar 2, 2020, 7:54:01 AM3/2/20
to golang-nuts
Axel,

I think it was a different kind of efficiency that first prompted my thinking on this.

The (my) original prompting code was written quickly, debugged, and working.  No tests.  Such is life.  The refactored "testable" code (no functionality added) became 162 lines and 9 funcs -- averaging only 18 lines per func.  From my standpoint, it cannot be read top-down -- the one public func is 21 lines.

I am comparing that to something I quickly wrote yesterday (deadline is soon, so has no tests), 7 funcs which average 66 lines per func.  I am sure that if I restructured it for tests that it would head towards 20 lines per func like above and therefore become 21 different funcs.  Like being hit with a hand-grenade!  :-)

Feels like there must be a better way.

Warren

Note to others: Software Engineers must operate with the 3-way tradeoff in mind (pick 2 is the old joke):
  1. quality (good)
  2. time (fast)
  3. cost (cheap)
Folks that always emphasize quality over time or cost may have an unrecognized bias that could be hurting their projects.  Recently my company archived lots of repos -- and my bet is that a great deal of the code worked fine and had few tests.  As Buddha said "The future is always other than you imagine it". 

One sign of possibly excessive quality bias is developers who don't want to show their code to anyone until it is "finished".

Warren Stephens

unread,
Mar 2, 2020, 8:05:16 AM3/2/20
to golang-nuts
Axel,

What if local var pointers (like x above) were invalid to list in a with statement?

Would there then be any other breaking examples?  Can it be broken using passed pointers or heap pointers?

Warren

Mikhail Gusarov

unread,
Mar 2, 2020, 8:21:25 AM3/2/20
to golang-nuts

On 2 Mar 2020, at 13:54, Warren Stephens wrote:

Note to others: Software *Engineers* must operate with the 3-way tradeoff


in mind (pick 2 is the old joke):

1. quality (good)
2. time (fast)
3. cost (cheap)

Well, if you are taking "engineering" out of sheaf, then the following essential
parts of any engineering project also deserve a mention:

  1. lifecycle (varying)
  2. stakeholder roles and their preferences

A. Throwaway write-once, execute-once code.

Stakeholder roles (often both roles are played by the same person) and their preferences:
- Developer. Write ASAP, debug ASAP
- Code user. Get the answer quickly and cheaply.

Does not need tests. with is irrelevant.

Z. Write once, maintain for 50 years code.

Stakeholder roles and their preferences (roughly):
- Original developer.
Write ASAP, debug ASAP, not to be hassled by maintainer and users.

  • Maintenance developer.
    Fix bugs, add features as expediently as possible. Not to break anything.

  • Operating engineer.
    Make the code work reliably to not be woken up during outages.

  • Technical writer.
    Provide documentation for maintenance developer, users, operator as fast as possible.

  • Users.
    Get the benefit, have bugs and features implemented as fast as possible.

  • Financier.
    Cost of development, operations and maintenance as low as possible.

  • Project manager.
    Time to fix bugs and add features as low as possible.

  • ... the list can go on and on

The price of writing the code for the first time and covering with tests if
it was written without tests in this case is irrelevant.

N. Code that was written once in a hurry, needs to be covered by tests, and does
not change often enough and radical enough to expose brittleness of the tests.

That's the sweet spot. Is there much code like this, and is this use-case
important enough?

--
Misha

Warren Stephens

unread,
Mar 2, 2020, 8:44:41 AM3/2/20
to golang-nuts
Misha,

Wonderful!  This view is the furtherest away from mere TDD "virtue signaling"!

Warren

Axel Wagner

unread,
Mar 2, 2020, 1:23:49 PM3/2/20
to Warren Stephens, golang-nuts
On Mon, Mar 2, 2020 at 2:05 PM Warren Stephens <wste...@prognoshealth.com> wrote:
What if local var pointers (like x above) were invalid to list in a with statement?

Would there then be any other breaking examples?  Can it be broken using passed pointers or heap pointers?

Of course. In fact, that was my thought process behind that code. It's obviously nonsensical as it is, to just compare pointers like that. But that comparison was supposed to signify exactly that situation, that you pass `x` and `y` to different functions, which then might or might not retain them (and might or might not break if they point to the same location or not).

You don't even have to pass them. Consider

var x *int

func F() {
    a, e := F()
    x = &e
    step with: a
    b, e := F()
    fmt.Println(x == y)
    _, _ = a, b // to "use" these
}

You also get the problem of "what's a pointer". Like, to actually help, slices, maps, channels and interfaces all would have to count as "pointers" too. Even structs would have to, if they *contained* any pointers. Pretty soon, you get to the point where you usually can't use `with`, because you'd have to use at least one of these restricted types.

I would be willing to be surprised - but I really don't believe this can be made to work. I can't *prove* it, but ISTM that in any case, you'd have to end up compiling it into separate functions anyway - and have it behave exactly the same way. At which point, the whole argument of avoiding the cost of function calls, or that refactoring is dangerous and might introduce breakage, just disappears - because it behaves exactly the same as separating things into functions anyway.
 

Warren

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.

Tristan Colgate

unread,
Mar 2, 2020, 1:39:28 PM3/2/20
to Warren Stephens, golang-nuts
While pure TDD as defined by its canonical text can be frustrating and time consuming, simply not writing tests is not really a defensible position, and definitely not one that Go, or any other language, should attempt to make easier.
  Writing testable code, and writing tests, does not have to mean TDD. In my experience it always results in better code, and us tasty slower to write because you spend less time in dead ends.
  I've done my share of time restricted, stream of consciousness programming. The results are almost always unmaintainable.
  Saying you should write tests, and write code intending to be tested, is the right kind of virtue signalling. Tests are one of the few things we have that gives any meaning to words Software Engineer.


--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.

Warren Stephens

unread,
Mar 2, 2020, 1:54:04 PM3/2/20
to golang-nuts
The over-arching goal is for me to write tests more easily.  Not avoid writing tests.  I am not arguing against tests.

Though I am being a bit snarky when I see responses that seem merely to say "Use TTD or something similar and it will solve all your problems!" 

I have never experienced that writing tests at the beginning saves time.  I find it is always faster to not write them in the beginning.  Functionality typically changes 3 or 4 times before it "settles down" enough that writing tests makes sense to me.

Warren

Warren Stephens

unread,
Mar 2, 2020, 1:56:47 PM3/2/20
to golang-nuts
Axle,

Maybe with would need to be some sort of "freeze the variables" (no redefinition) statement? -- I am going to think about it.

Warren

Robert Engels

unread,
Mar 2, 2020, 2:03:27 PM3/2/20
to Warren Stephens, golang-nuts
Then, as someone else pointed out, use black-box testing. Test only the public/external API. If your code is too complex to be tested this way, that's a sign of structure than needs to be refactored.

I don't think "it's the real world" will get much traction here - people that maybe started their careers the way you describe, changed their tune or left the business.

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.

Warren Stephens

unread,
Mar 2, 2020, 3:52:48 PM3/2/20
to golang-nuts
Robert,

I just feel like (somehow invariably!) breaking 60 line methods into 3 methods of 20 lines each is not the best ultimate solution.

As for the "real world" ...this is too funny.. I must live in a different one than most -- I have received 2 high-priority urgent requests since my previous post 1 hour ago!!!

The stuff I wrote even yesterday will have to wait.  Ha! ha!

Warren
 

Robert Engels

unread,
Mar 2, 2020, 4:33:09 PM3/2/20
to Warren Stephens, golang-nuts
That’s where people management. Seasoned vets are better off devoting energy to changing things than fighting fires - much better in the long run imo. 

On Mar 2, 2020, at 2:53 PM, Warren Stephens <wste...@prognoshealth.com> wrote:


--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.

Robert Engels

unread,
Mar 2, 2020, 6:33:52 PM3/2/20
to Warren Stephens, golang-nuts
In the specific case you cite, it seems doubtful to me that you couldn’t properly test a 60 line method fully externally/black box

On Mar 2, 2020, at 3:32 PM, Robert Engels <ren...@ix.netcom.com> wrote:


Message has been deleted

peterGo

unread,
Mar 3, 2020, 3:18:38 AM3/3/20
to golang-nuts
Fixed test.

Warren,


"The over-arching goal is for me to write tests more easily.  Not avoid writing tests.  I am not arguing against tests."

Here is a quick fix of your code;

ws.go: https://play.golang.org/p/KgQ0esiY0Xt

Now, use rdr to quickly test countSomeStuff:

ws_test.go: https://play.golang.org/p/K262-OIYa7e

ws
├── ws.go
└── ws_test.go

ws$ go test
PASS
ws$

Peter

On Monday, March 2, 2020 at 7:54:12 PM UTC-5, peterGo wrote:
Warren,

"The over-arching goal is for me to write tests more easily.  Not avoid writing tests.  I am not arguing against tests."

Here is a quick fix of your code;


Now, use rdr to quickly test countSomeStuff:


ws
├── ws.go
└── ws_test.go

ws$ go test
PASS
ws$

Peter

Bakul Shah

unread,
Mar 3, 2020, 3:58:37 AM3/3/20
to golang-nuts, Warren Stephens
On Mar 1, 2020, at 12:43 PM, Ian Lance Taylor <ia...@golang.org> wrote:
>
> On Sun, Mar 1, 2020 at 8:21 AM Warren Stephens
> <wste...@prognoshealth.com> wrote:
>>
>> // I don't want to get into the specific syntax of the test code directly,
>> // but a test would start execution here, supplying "lines" and "thing2"
>> //
>> mystep2 with: lines, thing2 // <---------- hide all local variables from above except for lines and thing2
>>
>> mymap := make(map[string]int)
>> for _, line := range lines {
>> if strings.Contains(line, thing2) {
>> count := strings.Count(line, thing2)
>> mymap[line] = count
>> }
>> }
>>
>> // and the test would end here -- because the next line is another "with" (or return) statement
>> // it would be able to do Asserts and such on the variables within the scope of this code segment
>>
>> Ian,
>>
>> An alternative approach would be to have:
>>
>> mystep2 with: lines, thing2 => mymap
>>
>> to specify all of the intermediate variables of interest for the test.
>>
>> These testable code chunks, delimited by with statements, act like hidden funcs. The actual call could be something like test_doSomeStuff@mystep2
>
> To me this all seems very unlike anything else in the Go language.
> There is nothing syntactically similar to this anywhere else. I think
> it is extremely unlikely that Go would adopt this idea.

What Warren wants seems something like "Hoare Logic", where you have a
notation such as

P{C}R

where P & R are assertions and C is a block of code. If precondition P
is satisfied, after executing C the postcondition Q will be satisfied.
This can be used to build up in a stepwise manner the correctness "proof"
of a function.

See C.A.R.Hoare's 1969 paper, "An Axiomatic Basis for Computer Programming"
http://extras.springer.com/2002/978-3-642-63970-8/DVD3/rom/pdf/Hoare_hist.pdf

You can already do the equivalent of this in Go today if you want. IMHO any
additional syntax doesn't seem to buy anything. If my guess is wrong,
perhaps Warren can try to clearly describe the exact *semantics* he wants?
[Always a good idea!]

Warren Stephens

unread,
Mar 3, 2020, 7:15:36 AM3/3/20
to golang-nuts
I really am not trying to be a pain about this "real world" thing -- think about trauma nurses and MASH surgeons.  I have 2 friends that are trauma nurses who fly on rescue helicopters to car and motorcycle crashes (or occasionally a military location).  They do not do things the same was as regular hospital nurses, and never will.  That is not a people management problem.  One of them is literally a "seasoned vet" of the National Guard.

In my current project (which runs 24x7, like most things in my multi-decade experience, and for which I am "on call"), I have not found a single problem using programmed tests.  Sure I need to add some, but all of the real problems have been found by running "battlefield stress" tests -- launching it 10,000 times rapidly to find, for instance, that the cloud vendor will sometimes unceremoniously kill the process at a random point in the program and restart it.

The Agile Manifesto is all about cycling on working code.  My belief is that every cycle does not need test processes.  Add them over time, when the dust occasionally settles. 

roger peppe

unread,
Mar 3, 2020, 12:49:26 PM3/3/20
to Warren Stephens, golang-nuts
> In my current project [...], I have not found a single problem using programmed tests.

Automated tests play more roles than just finding initial problems. Arguably more important is to guard against errors introduced when maintaining and updating the code over time (regression). Tests are also a liability as well as an asset. Sometimes updating tests when behaviour changes can be very costly.

For the above reasons, I consider that when possible, it's much better to write tests in terms of behaviour that really matters (using the public API, as Robert Engels suggests) rather than using details of the internal implementation.

If I wrote a bunch of tests using this "with" statement, then refactored the code, how useful would those tests be? Would they guard against behaviour regression? Would they really be more of an asset than a liability over time?

Of course, it's not always possible to test using the external API, but at least if you're testing a function, there's an obvious entry point and implied contract that can be documented and maintained. That's not the case if your tests are poking directly into local variables within the code.

In other words, I see this aspect of Go as appropriate design pressure towards making more maintainable software, not a restriction that needs to be worked around.

  cheers,
    rog.

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.

Warren Stephens

unread,
Mar 3, 2020, 5:37:40 PM3/3/20
to golang-nuts
rog,

Very well said -- but everyone keeps talking about improving the maintainability without talking about the "comprehensiblity".

Look at the 2 things below.  Which is more quickly comprehensible?  We cannot conceive of a way to have the upper one and still test each piece???

Warren

AAA
BBB
   www
CCC
   xxx
      yyy
DDD
   zzz

===========

AAA
BBB
www
CCC
xxx
yyy
DDD
zzz



Jason E. Aten

unread,
Mar 3, 2020, 6:25:32 PM3/3/20
to golang-nuts
Gentle suggestion: put each step you want to be testable into its own function.

AAA
BBB
   www()
CCC
   if xxx() {
      yyy()
   }
DDD
   zzz()

Dan Kortschak

unread,
Mar 3, 2020, 6:28:38 PM3/3/20
to golan...@googlegroups.com
The answer to that question is entirely dependent on context, which is
stripped by using anonymous labels as you have. For linear things, the
second one is clearer, for hierarchical things the first is.

It is entirely possible to test each piece of an hierarchical
structure; this is the basis for unit testing. If you take a look at
how Go authors conventionally write helpers (you'll see this throughout
the stdlib and in the golang.org/x packages) the naming conventions
foster an intelligibility, while factoring out code and so 1. making it
testable, and 2. preventing the details getting in the way of the
bigger picture (at each hierarchical level).

To be perfectly frank, providing a mechanism that allowed people to put
more code into functions, as the with system you're proposing appears
to, would IMO harm intelligibility and I'd reject code that used it in
PRs.

Dan
> --
> You received this message because you are subscribed to the Google
> Groups "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it,
> send an email to golang-nuts...@googlegroups.com.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/golang-nuts/be59057f-bd56-4b57-9e23-3c59b1153a47%40googlegroups.com
> .


Axel Wagner

unread,
Mar 4, 2020, 2:49:07 AM3/4/20
to Warren Stephens, golang-nuts
On Tue, Mar 3, 2020 at 11:38 PM Warren Stephens <wste...@prognoshealth.com> wrote:
Look at the 2 things below.  Which is more quickly comprehensible?  We cannot conceive of a way to have the upper one and still test each piece???

I don't really understand the point you are trying to make here. To me, the upper one seems more comprehensible and represents what has been suggested in this thread many times: To factor out the individual, separable pieces of code into their own functions with their own contracts and test those. As opposed to have one long run of statements.

Maybe that's an artifact of imprecise representation and abstraction - i.e. maybe I don't understand correctly what you are trying to show with what you draw out. But maybe, it's also an artifact of ideas like "comprehensibility" (just like "readability") being overwhelmingly subjective. Sure, there is a long tail of clearly bad code where enough people can agree that it's incomprehensible, that we can talk about it being "objectively" bad. But for the overwhelming majority of realistic code where these questions come up, in my experience, reasonable people can agree that either version would work fine and that it mostly comes down to personal preference.

Personally, my working memory is severely limited. So, having separate functions, allows me to treat them as individual units of comprehension, that I can then compose into larger blocks, a few at a time.

(also, I want to once again point out that talking about "the real world" and your "decades of experience" comes of as condescension, even if it isn't intended as such. I don't think that serves your intention to convince people of your view point)


Warren

AAA
BBB
   www
CCC
   xxx
      yyy
DDD
   zzz

===========

AAA
BBB
www
CCC
xxx
yyy
DDD
zzz



--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.

roger peppe

unread,
Mar 4, 2020, 3:24:12 AM3/4/20
to Warren Stephens, golang-nuts
On Tue, 3 Mar 2020 at 22:37, Warren Stephens <wste...@prognoshealth.com> wrote:
rog,

Very well said -- but everyone keeps talking about improving the maintainability without talking about the "comprehensiblity".

Look at the 2 things below.  Which is more quickly comprehensible?  We cannot conceive of a way to have the upper one and still test each piece???

I'm interested to try to understand why you feel you can't write tests for the former code by calling the top level entry point
(presumably AAA in your example). I feel that so many testing decisions are only properly made in the context of the code itself. It's hard to talk usefully about such things without any actual examples.  Perhaps you could provide some Go code that you'd feel that you'd be forced to split up in order to test (perhaps reduced in size or anonymized), so that we have something concrete to talk about?

  cheers,
    rog.


Warren

AAA
BBB
   www
CCC
   xxx
      yyy
DDD
   zzz

===========

AAA
BBB
www
CCC
xxx
yyy
DDD
zzz



--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.

Tristan Colgate

unread,
Mar 4, 2020, 3:26:45 AM3/4/20
to Warren Stephens, golang-nuts
It is not possible to infer the intent of a chunk of code purely from
the expressions. You can see what it does, not what it meant to do.
One way of labeling the intent is to put that code in a function. A
well chosen name can let someone determine what the function was
intended to do, and fix it if it is doing something else. You can also
test that intention holds for some reasonable set of values, and
people can expand that set of values if they find ones for which that
function seems to do something else.

So, if the choice you propose is between an function with a series of
steps, all in line, vs a function with a series of named steps that
are elsewhere, then yes, I'll go with you're option one because I'd
rather read.

func (a *thing) processThing() error{
a.tidyTheThing()
a.mungeTheThing()
a.augmentTheThing()
}

Than a function of potentially unbounded length which is just a frenzy
of activity on that thing.

The problem is that you are advocating for what most people would
consider to be the day 1 programming experience, where experience and
industry best practice has show us that it is better to structure
things more clearly. You are the first person I've seen actually
advocate for this approach, where my own experience, and the
experience of every text I've read, advocates for the opposite.
> --
> You received this message because you are subscribed to the Google Groups "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/be59057f-bd56-4b57-9e23-3c59b1153a47%40googlegroups.com.



--
Tristan Colgate-McFarlane
----
"You can get all your daily vitamins from 52 pints of guiness, and a
glass of milk"

Warren Stephens

unread,
Mar 4, 2020, 6:32:12 PM3/4/20
to golang-nuts
Folks,

First off thanks for the feedback!  -- positive and negative.

Second, hopefully people can trust that I fully understand the standard approach to code refactoring and writing tests.  I have done a bunch of it, and am sure will continue to do a bunch of it.  I understand the merits of it.  It is a valuable thing to do for the long run.  No pejorative is intended here.

That said, I am trying to reset here with another example about a possible new way to have good tests AND have code for which it is easy to quickly understand the relationships between all sub-tasks of the code -- it is modified code from The Go Programming Language book.  Note: There is a bunch of "goto nonsense" added to get the Go compiler to accept the 4 labels for compiling.

The attributes that I would like to highlight are:
  1. a single function which accomplishes a complete task
  2. which is composed of ordered steps
  3. where some steps may have sub-steps, and perhaps sub-sub-steps
Observations:
  1. You can put this code into a modern editor with block expansion and contraction, and contract the 3 main steps to easily show (without scrolling) steps 1 to 3.
  2. You can then expand step2 alone and see how step2A is clearly a sub-step of step2.
  3. You can also imagine how other sub-steps and sub-sub-steps would maintain a quasi-flow chart relationship to each other -- top to bottom, with indentation, etc.
  4. You can also imagine that each step and sub-step could be a proper func if refactored out of the parent func.
Aspirations:
  1. Go language could be extended in some way that tests could be added to steps WITHOUT refactoring the steps or sub-steps.
  2. Adding such tests could accomplish the same effects as refactored code, but WITHOUT losing the quasi-flow chart relationship of the steps.
  3. Adding such tests could be faster to write by avoiding the refactoring effort.
  4. Adding tests this way could potentially produce faster running code by avoiding the extra calls required by refactored code.
  5. There is some possibility that a language extension could highlight or define the specific variables that are most important to comprehend while reading thru the code.
  6. There is some possibility that a language extension could allow vars (such as 'err') to be redefined per step, rather than the sometimes problematic "fall-thru" definition of vars in prior code.
Critiques welcome,

Warren

package main

import (
"fmt"
"sync"
)

func main() {

fmt.Printf("Hello folks!\n")
err := doSomeStuff(true)
if err != nil {
fmt.Printf("doThisStuff did not succeed! %v\n", err)
}
fmt.Printf("Goodbye folks!\n")
}

const N = 20

var naturals = make(chan int, N)
var squares = make(chan int, N)

func doSomeStuff(dummy bool) error {

var n sync.WaitGroup

// there is a bunch of nonsense added here to get the Go compiler to accept the 4 labels

// Counter
step1:
if dummy {
n.Add(1)
go func() {
for x := 0; x < N; x++ {
naturals <- x
}
n.Done()
}()
n.Wait()
if !dummy {
goto step1
}
}

// Squarer
step2:
if dummy {
n.Add(1)
go func() {
for x := 0; x < N; x++ {
v := <-naturals
squares <- v * v

// Squarer complainer
step2A:
if v > 1000000 {
fmt.Println("v is too big!")
v = 0
goto step2A
}
}
n.Done()
}()
n.Wait()
if !dummy {
goto step2
}
}

// Printer
step3:
if dummy {
n.Add(1)
go func() {
for x := 0; x < N; x++ {
n := <-squares
fmt.Println(x, n)
}
n.Done()
}()
n.Wait()
if !dummy {
goto step3
}
}

return nil
}


Again, it will be easier to understand the idea if the code is copied into an editor with a block expansion and contraction feature.


Tamás Gulácsi

unread,
Mar 5, 2020, 1:25:46 AM3/5/20
to golang-nuts
If the (sub)step alone is comprehensible just in itself, hiding the previous (sub)steps and its context, then it is comprehensible when refactored to a separate function.

An then it is separately testable, documentable, and no collapsing esitor help is neede when viewing them main function, listing those (sub)steps.

I understand that refactoring is a risk, but adding a new language feature just to keep the current unstructured code, is plain wrong.

roger peppe

unread,
Mar 5, 2020, 3:06:25 AM3/5/20
to Warren Stephens, golang-nuts
On Wed, 4 Mar 2020 at 23:32, Warren Stephens <wste...@prognoshealth.com> wrote:
Folks,

First off thanks for the feedback!  -- positive and negative.

Second, hopefully people can trust that I fully understand the standard approach to code refactoring and writing tests.  I have done a bunch of it, and am sure will continue to do a bunch of it.  I understand the merits of it.  It is a valuable thing to do for the long run.  No pejorative is intended here.

That said, I am trying to reset here with another example about a possible new way to have good tests AND have code for which it is easy to quickly understand the relationships between all sub-tasks of the code -- it is modified code from The Go Programming Language book.  Note: There is a bunch of "goto nonsense" added to get the Go compiler to accept the 4 labels for compiling.


The attributes that I would like to highlight are:
  1. a single function which accomplishes a complete task
  2. which is composed of ordered steps
  3. where some steps may have sub-steps, and perhaps sub-sub-steps

I find your code to be a somewhat odd example to illustrate your point.
  • It's structured as a set of goroutines communicating over channels, but you've arranged things so that there is no concurrency - each goroutine runs from start to finish before the next one is started, so the goroutines and channels are entirely redundant. You might as well have used slices instead of channels.
  • The behaviour that you're wanting to test isn't easily available for testing, even if something like your proposal was accepted, because the only observable effects of the program involves printing to the standard output.
  • If the program was changed to be concurrent in the way that its structure suggests, then it no longer has the attributes that you're highlighting - it would no longer be composed of ordered steps, but of concurrent operations instead.
  • Programs composed of goroutines communicating over a channel are perfect for testing as independent functions, and arguably your program is easier to understand if it is broken up as such.
  • The entire example would actually be easy to test from the top level without testing the individual parts if the use of stdout was changed.
  • If you did test each sub-step, then the bug in step 2A would never be found, because it's only a bug when the code is used in context. Only then does it become clear that the assignment to v does nothing because that local variable is immediately thrown away afterwards.
Having said all that, I believe there is a valid point to be made with regard to testing concurrent Go programs that have long-lived behaviour over time. It can be hard to test such programs, and I've not yet seen an approach that doesn't feel clunky and/or somewhat error-prone. However, as far as I can see, your proposal wouldn't help in that respect.

Warren Stephens

unread,
Mar 5, 2020, 8:42:14 AM3/5/20
to golang-nuts
All,

If the code here is not making sense then take a look AWS Step Functions.


And imagine something that is compiled, rather than the web service that AWS offers.


AWS Step Functions can be editing in a conventional way, or can be edited visually.

A JSON object moves thru the flow-chart, and elements can be added or removed.

Each block of the Step Function can be tested independently of the others by passing in a test object and examining the output object.

I doubt that Go will make this type of leap -- but my bet is that some language will do so in the future.

Warren

Robert Engels

unread,
Mar 5, 2020, 9:08:01 AM3/5/20
to Warren Stephens, golang-nuts
It’s in the name “Step FUNCTION”. They are composable testable units. This is what people are telling you to use. 
--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.

Warren Stephens

unread,
Mar 5, 2020, 9:26:36 AM3/5/20
to golang-nuts

And I am saying Functions PLUS Structure.


Functions remain.  Visible structure is added.


Warren


David Riley

unread,
Mar 5, 2020, 10:26:52 AM3/5/20
to Warren Stephens, golang-nuts
On Mar 2, 2020, at 1:54 PM, Warren Stephens <wste...@prognoshealth.com> wrote:
>
> I have never experienced that writing tests at the beginning saves time. I find it is always faster to not write them in the beginning. Functionality typically changes 3 or 4 times before it "settles down" enough that writing tests makes sense to me.

I'm not a TDD stan in the least, but I have to argue here that if this is the case, you're not spending enough time on requirements and use cases up front. You should be able to write a specification that can be tested against; if that changes in huge, significant ways such that you have to rewrite all your tests, you don't have a spec, you have a draft. I'm aware that that is the reality for a lot of engineers, having worked under both kinds of domains, but that doesn't make it right.

Similarly, if your problem with writing tests for your own code refactored by someone else is that you can't understand the dataflow anymore, then the problem is that someone somewhere along the line has not sufficiently documented the code. The solution is not to enable the retroactive insertion of crutches into the internals of a function, especially since tests should be verifying that the external interfaces of the unit under test conform to the specification. Internals should not even be visible to tests, which is why they usually aren't.

IMHO we should not be modifying Go to enable poor engineering methodologies, and reactive design is absolutely one of those.


- Dave

David Riley

unread,
Mar 5, 2020, 3:51:11 PM3/5/20
to Warren Stephens, golang-nuts
On Mar 5, 2020, at 9:26 AM, Warren Stephens <wste...@prognoshealth.com> wrote:
>
> And I am saying Functions PLUS Structure.
>
>
>
> Functions remain. Visible structure is added.

Right, and you can do your testing at the boundaries of those functions, because the structure is nothing but a shell (in most Step Lambda cases, just a state machine) that runs the lambdas.

That's what's also suggested for how one should structure Go code for testability; if you want to test something, it should be externally visible as part of the established, documented interfaces. If you're trying to externally test function internals, you are inevitably setting yourself up for a lot more maintenance headaches down the road because the internals of a function are not (or should not be) subject to control by the specification.

Consider the testing approach defined by Cucumber (whose excellent Go implementation is available as "godog"). You can actually write the entire specification as a series of tests, if you wanted to. But the point is that it tests as a set of externally-observable behaviors, not relying on "this magic internal value should be set to this value".

It will save you a LOT of time in the end, even if it requires significant mental effort and dragging people along with you. I spent the last year and a half trying to push changes like that at my org, and it was beginning to show tangible benefits right before the project got shut down for other reasons. Making your tests organized, consistent, readable and repeatable will make your life so much easier in the end, even if it seems insurmountable right now.

And if your org is resistant to spending the time necessary to clear away the technical debt that's been built up by (probably decades of) lax engineering approaches? It's probably a sign that either you need to put your foot down more and/or sell it by noting that reduced defects means increased velocity and less embarrassing fires to put out, or you should start polishing your resume, because there are many orgs out there that will probably pay you more to do it right.


- Dave

Warren Stephens

unread,
Mar 5, 2020, 5:17:36 PM3/5/20
to golang-nuts
Dave,

A key amount of testing here and there is a great thing, but I think that folks greatly underestimate the amount of time (per your example) and effort put into increasing test coverage only to have a project shut down!  There often is no "long run"!  Whereas, what I call "real world" testing -- like launching something 10,000 times quickly (50,000 times today!) in the actual cloud infrastructure have produced far far more benefits to me on a time/effort basis -- like when I found that I can get a zero return code and the action did not succeed anyway.  It happens!

I believe there is a principle at play here where something that is "teachable" appears to be more important than something that isn't "teachable".  Like statistics. ha ha!

The other thing is that I do not see much difference between what you (and others) refer to as "internal" versus "external".  When I refactor a bit of code out then it basically looks the same to me.  And a step in a Step Function can call other step functions -- so you don't know whether you are being "internal" or "external" really -- like a fractal.

So I should invent a fractal programming language -- where every function must be defined within another function -- have people write tests for that!

Best,

Warren

David Riley

unread,
Mar 5, 2020, 5:22:39 PM3/5/20
to Warren Stephens, golang-nuts
On Mar 5, 2020, at 5:17 PM, Warren Stephens <wste...@prognoshealth.com> wrote:
>
> So I should invent a fractal programming language -- where every function must be defined within another function -- have people write tests for that!

I think we call that "LISP".


- Dave

Bakul Shah

unread,
Mar 5, 2020, 5:38:01 PM3/5/20
to roger peppe, golang-nuts
On Mar 5, 2020, at 12:05 AM, roger peppe <rogp...@gmail.com> wrote:
>
> Having said all that, I believe there is a valid point to be made
> with regard to testing concurrent Go programs that have long-lived
> behaviour over time. It can be hard to test such programs, and I've
> not yet seen an approach that doesn't feel clunky and/or somewhat
> error-prone.

Dijkstra wrote "Testing reveals the presence, not the absence of bugs".
This is particularly true of concurrent programs! How do you ensure there
are no deadlocks? There may be too many states to test. And debugging is
even harder. How do recreate conditions for a deadlock when you don't
really know what may be the cause?

In contrast formal specification languages/tools such as TLA+/Coq/SPIN
seem to have made better headway. It would be good if specification
support can be integrated into Go. Or may be, have a separate language
analogous to the Bluespec language that can be compiled to Verilog.



Dan Kortschak

unread,
Mar 5, 2020, 5:41:58 PM3/5/20
to golan...@googlegroups.com
I'm really struggling to understand the benefit that you say you'll
get. The linear form that the with: label gives you is really just what
we already use with a different accent. The cost of testing or not is
not substantially different, but the cost of allowing long linear
functions, needing additional editor support, requiring additional
concepts for language users and the compiler, and not being able to use
already well establish refactoring tools that are well understood and
tested, all make it feel like a net loss of benefit.

All of the things that you are wanting to do with the with:
label/directive can be done with functions, arguably in more
cognitively friendly way (for example in you with: examples you use
comments to explain what each stanza does, making room for divergence
between commentary and code; using functions, the function name *is*
the comment).
> --
> You received this message because you are subscribed to the Google
> Groups "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it,
> send an email to golang-nuts...@googlegroups.com.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/golang-nuts/90922f81-d536-4598-98b3-7b1b612c678b%40googlegroups.com
> .


Warren Stephens

unread,
Mar 6, 2020, 3:59:29 AM3/6/20
to golang-nuts
establish[ed] refactoring tools that are well understood and
tested

New modes of working need new tools to be developed.  Prior paradigm tools are for the prior paradigm.

How many good tools exist now that will turn linear code into a nice looking readable flow chart?  Few? None really?  Take a look at the LabView interface.  Google "LabView" and click on "images" -- those visual things are programmable.  Clicking on something will take you inside it, where the things inside are programmable as well, and so on.


The comments in the code above were in the original code from the book.  

But that reminds me -- another idea that I have is to merely add special comments to code.  These comments would have a formal structure that would tell how to arrange the associated block of code in a graphical fashion.  Something that would be a bit like UML, but less heavy handed.  Something that could be "thinly" added a bit at a time to existing code that would allow a flow-chart to be "extracted" directly from the source.  These special comments would also work across languages -- almost every source file in a project allows comments (get on it JSON!).

Imagine getting a new employee and telling her/him to "go add markup to the such-and-such project source code until the grapher tool can extract a nice high-level diagram".  The employee would learn the code while producing the nice diagram -- allowing others to quickly "see" the structure of the project.  Add 3 of these special comments, and you would get a 3 block diagram.  And 300 of these special comments, and you would get a large complicated thing tying almost everything together.  A "linter" would come along and tell you which comments don't really match its containing block quite correctly.

That approach would not be as advanced as a real visually oriented programming language, but would be rather useful, and a step in the right direction.

Best,

Warren

Dan Kortschak

unread,
Mar 6, 2020, 4:39:16 AM3/6/20
to Warren Stephens, golang-nuts
I wrote cybernetic systems for laboratories with LabView a few decades
ago. Nothing is worth keeping from that system.

Warren Stephens

unread,
Mar 6, 2020, 4:45:52 AM3/6/20
to golang-nuts
A few decades ago the Apple Macintosh was not very good either.

Warren

Nigel Tao

unread,
Mar 6, 2020, 6:24:34 AM3/6/20
to Warren Stephens, golang-nuts
On Thu, Mar 5, 2020 at 10:32 AM Warren Stephens
<wste...@prognoshealth.com> wrote:
> There is a bunch of "goto nonsense" added to get the Go compiler to accept the 4 labels

Well then, don't use labels, so you don't need nonsense (or the dummy
parameter).

Cutting them out (https://play.golang.org/p/SUalTGaAxmx) runs just
fine, and pretty much lets you do what you want (even if most other Go
programmers, myself included, would write it differently), right? For
example:

> You can put this code into a modern editor with block expansion and contraction, and contract the 3 main steps to easily show (without scrolling) steps 1 to 3.

https://play.golang.org/p/SUalTGaAxmx has blocks, and presumably your
editor can contract them.


> You can then expand step2 alone and see how step2A is clearly a sub-step of step2.

Well, step2A is clearly a sub-step of step2 because of indentation and
being within step2's {} curly braces. I don't think the "step2A"
label/comment really adds much.

Warren Stephens

unread,
Mar 6, 2020, 7:42:51 AM3/6/20
to golang-nuts
Nigel,

Thanks!  I can use that in general to wrap pieces of code so that I can collapse them in my editor whenever I want!

I was grasping for some concrete way to show this concept -- amid the distraction of doing real work.  

Perhaps I should have referenced MIT's Scratch programming tool???  "Go is not as good as Scratch" -- THAT would be popular around here I am sure!

My kids learned using Scratch when they were little.

Warren

Dan Kortschak

unread,
Mar 6, 2020, 3:37:47 PM3/6/20
to Warren Stephens, golang-nuts
Is this helpful for addressing your issue?
Reply all
Reply to author
Forward
0 new messages