The pipeline documentation at https://jenkins.io/doc/book/pipeline/overview/ provides a good start for understanding how to write a Jenkinsfile. However, there is other information that is almost essential for understanding how to do this that isn't found on this page (or the "Jenkinsfile" page, an even more appropriate place for this).
Specifically, the Groovy mechanisms that simply will silently cause the pipeline to exit, without any information.
If you dig around the various sites, you can find pages like https://github.com/jenkinsci/workflow-cps-plugin/blob/master/README.md that hint at the details. The actual full details on the restrictions and alternative strategies don't appear to be written down anywhere, or not in the place a beginner would expect. This information should be COMPLETELY spelled out in the "Jenkinsfile" page.
I would suggest changes to the doc, but I couldn't do that until I can convince myself that I fully understand the restrictions myself. In the last several days, I've had to figure out by testing what works and doesn't work by random chance.
I've even noticed at least one inconsistency with the statements that I have found. For instance, I've read that you can't execute "pipeline steps" within a @NonCPS-annotated method. I've also been told that every choice in the "Snipper Generator" dropdown is a pipeline step. One of the choices in that list is "echo". I've verified that "echo" works fine in a @NonCPS method (I've also noted that "println" appears to be an alias for this). Are there other choices in that dropdown that work fine in a @NonCPS method? I'm not about to test all of them.
Something else that could be described in more detail is what list manipulation strategies are legal, whether in a @NonCPS method or not. For instance, in a @NonCPS method, I determined that "collection.get(index)" acts like a pipeline step, just exiting the pipeline. I also found that most of the ways you can append to a list are also apparently "pipeline steps". Doing "collection.add(item)" or "collection << item" behave the same. I managed to discover that "string.split()" works fine, so any mechanism where I had to append to lists got translated to appending to a string and then "splitting" to the list.
And on a question outside of the documentation, is there some reason executing pipeline steps in a @NonCPS method has to fail so silently? Shouldn't that just be considered an error that makes the pipeline fail with a more reasonable message?