Repository navigation
🔍 Examples of Generated Samples for Code Review [DO NOT MERGE] - #6543
beccasaurus wants to merge 1 commit into
Conversation
|
I would really like use to The long-term plan is to have all samples (generated and hand-written) live in the |
+1 to this. I'm also still strongly against the idea of having the primary way of having the user execute these be via the command line. It's not idiomatic and directly contradicts the idea of "copy-paste-runnable", which is one of the desirable goals that came out of the samples format working group. If we have to include an interface via CLI, it should be additive and not have any effects on what the sample would look like without it. |
| public class LanguageClassifyGcs { | ||
| // [START language_classify_gcs] | ||
| /* | ||
| * Please include the following imports to run this sample. |
There was a problem hiding this comment.
Having the imports listed again is very distracting.
| * | ||
| * @param gcsContentUri Google Cloud Storage URI where the file content is located. e.g. | ||
| * gs://[Your Bucket]/[Path to File] The text file must include at least 20 words. | ||
| */ |
There was a problem hiding this comment.
I don't think we should be using java-doc format for these. It's overly verbose, and no one is actually generating docs from these files so we aren't reaping any benefits.
| */ | ||
| public static void sampleClassifyText(String gcsContentUri) { | ||
| try (LanguageServiceClient languageServiceClient = LanguageServiceClient.create()) { | ||
| // gcsContentUri = "gs://cloud-samples-data/language/classify-entertainment.txt"; |
There was a problem hiding this comment.
Java-docs-samples is moving from commented out variables to declaration with an overloaded function instead. See GoogleCloudPlatform/java-docs-samples#1582 for an example.
There was a problem hiding this comment.
Even as we move to over-loaded, there should still be a String at the front.
// String gcsContentUri ...
There was a problem hiding this comment.
or var as its more modern. (but only in comments until we move to Java10+ as a base version
| try (LanguageServiceClient languageServiceClient = LanguageServiceClient.create()) { | ||
| // gcsContentUri = "gs://cloud-samples-data/language/classify-entertainment.txt"; | ||
|
|
||
| // Available types: PLAIN_TEXT, HTML |
There was a problem hiding this comment.
+1 to doing this. Additionally, it would be great if a link to someplace in the docs that would give more context on what these values are (Particularly for the libraries that don't have an enum type, and instead just use magic string values).
| public class LanguageClassifyGcs { | ||
| // [START language_classify_gcs] | ||
| /* | ||
| * Please include the following imports to run this sample. |
There was a problem hiding this comment.
Why are we including the imports as a comment instead of outside of the class?
| import org.apache.commons.cli.Options; | ||
|
|
||
| public class LanguageClassifyGcs { | ||
| // [START language_classify_gcs] |
There was a problem hiding this comment.
The current sample format has these include everything but the package name to make it easier for users to copy/paste.
| * gs://[Your Bucket]/[Path to File] The text file must include at least 20 words. | ||
| */ | ||
| public static void sampleClassifyText(String gcsContentUri) { | ||
| try (LanguageServiceClient languageServiceClient = LanguageServiceClient.create()) { |
There was a problem hiding this comment.
This should have a comment explaining why it's important to use the try-with-resources. Often users are going to be reusing a single client, so it's more likely they'll want to call close(). See the Client Initialization.
There was a problem hiding this comment.
I.e. is this an initialized thing that I can use throughout the life of my app? is it thread-safe? (or do I need one for each thread (ie. request))? Is create an expensive (time consuming) operation?
All these things developers need guidance on.
| .setLanguage(language) | ||
| .build(); | ||
| ClassifyTextRequest request = ClassifyTextRequest.newBuilder().setDocument(document).build(); | ||
| ClassifyTextResponse response = languageServiceClient.classifyText(request); |
There was a problem hiding this comment.
Can we add some clarification here that the request isn't actually sent until classifyText is called? I feel like that's an easy mistake to make.
Also a nano style nit, but I would typically expect to see a newline between the request being built, the request being sent, and the response being processed. These feel like 3 very different actions, and seeing them together as single block makes it feel a little harder to parse (but maybe that's just me).
There was a problem hiding this comment.
Some kind of comment about expected latencies here would be helpful.
| // is that this category represents the provided text. | ||
| System.out.printf("Confidence: %s\n", category.getConfidence()); | ||
| } | ||
| } catch (Exception exception) { |
There was a problem hiding this comment.
This exception is way too broad - we should only be catching exceptions that we have to. For those exceptions, we should be showing the user how to correctly handle them.
@lesv is working on a proposal to clarify which errors/exceptions we should handle and what to do with them.
There was a problem hiding this comment.
TL;DR; we should identify expected exceptions and provide either suggested mitigations (if any) or a clear explanation of what happened, if we aren't really doing anything useful, then we shouldn't be catching it.
| // sample-metadata: | ||
| // title: Classify Content (GCS) | ||
| // description: Classifying Content in text file stored in Cloud Storage | ||
| // usage: gradle run -PmainClass=com.google.cloud.examples.language.v1.LanguageClassifyGcs [--args='[--gcs_content_uri "gs://cloud-samples-data/language/classify-entertainment.txt"]'] |
There was a problem hiding this comment.
This should have example usage for maven and gradle.
/cc @lesv
There was a problem hiding this comment.
Wow that's a horrifying command line. Great for cutting and pasting, but folks really aren't likely to get it right all that often. Is a gradle.build a part of the generated output?
I'm ok with just gradle, but there needs to be something, and it should be easier than the -PmainClass=... line you have.
|
Just to clarify a few points about sample-tester:
We are on the same page with the standalone samples being copy-paste-runnable, which is why if you compile these with javac they ought to just give you an executable and work. Is your comment alluding to the fact that working on the command line is itself not idiomatic in Java? If so, let's figure out a way to make execution idiomatic, though I would still hope to allow (but not require) each sample to be invoked via the CLI. I'm not an expert on the Java community, so your feedback is very valuable! |
When I say "copy-paste-runnable", I mean copying the snippet from a page in our documentation, pasting it into a local file, and making the smallest amount of changes to run it. The vast majority (like 97%) of all Java users use either IntelliJ, Eclipse, or NetBeans as an IDE. Thus, I expect that most developers will likely first try to just copy and paste the snippet from the doc page they are looking at, and update the initial variables. They may also try to integrate it into existing code if they are prototyping a solution. Our top priority with snippets should be getting users to understand how the call works, not just making sure they are able to execute it. My biggest issue with executing CLI is that historically it has added extra noise to the snippets that the user then has to shift through. Additionally, adding a CLI has often led to a README that only includes instructions for running passing arguments via the command-line, instead more general descriptions of what needs to be set or filled in to get the sample running. In an ideal world, the README would tell me which lines I need to change, and then let me to run the function however I prefer. If we can figure out how to
Most users will expect either maven or gradle to handling building and verifying the code. If I was trying to use this snippets to read from the API, my first instinct would be to copy-paste the code as I mentioned above. If I wasn't able to get it to run with minor debugging, my next step would be to clone the repo and run If after messing around for a while and I happened to figure out that I needed a to download some random binary to run the tests, I would likely already have wasted a bunch of time and would be feeling frustrated that the owners felt the need to do something different.
I'm not sure if there are specific features you have in mind, but for java-docs-samples we've already established a pretty consistent pattern of capturing stdout, running a snippet's function, then checking that the correct output has occurred. This seems to be a similar surface to what the sample-tester is testing right now, but would allow for more idiomatic execution on per language basis. |
|
Thanks for the info!
Thanks for bringing that up. I should clarify that my earlier statement about copy-paste-runnable applies in the strictest sense only to the full source code of the sample: we mean for the samples to be ready to execute from the full file in the GitHub repo. So far, our strategy in all languages has been to place the region tags so that only the sample function (or just its body, depending on the language) gets displayed on cloudsite, while omitting the This strategy matches what is displayed by the current hand-written samples (cloudsite example) and reduces the boilerplate that people have to wade through when looking at the documentation on the website: we figured people would want to get right to the point. In that sense, the samples are not quite copy-paste-runnable from devsite and require some tweaking in most/all languages, which is why we have some of the generated comments (imports in Java, default variable assignments in languages where the region tags exclude the sample function signature). We are, of course, happy to change the location of the region tags if that's what DevRel needs. That's the purpose of this PR ;-)
Fair enough. But if the sample did not include boilerplate or a
Good to know. If you're already calling the repo, how would you feel if you could still call
That is mostly, but not entirely, what As an additional point, the sample-tester also tests not just that the sample function works, but that it is indeed copy-paste runnable (from source) as advertised. |
|
@vchudnov-g just to note, those language samples you point to were written back in 2017. Our style rules have changed since then. They aren't the best representation of our current hand-written samples. |
To further add to this, the DPEs had a working group last year that spent several months establishing ideal sample formats, creating canonical examples, and getting language specific stakeholders to sign off. Many different issues were discussed, debated, and compromised on during this process. It was also stated that these samples formats would be used as a target for these groups, but unfortunately this doesn't seem to be the case since many these same discussions are being revisited.
All three of the IDEs mentioned above allow you to simply click on a function and execute it without having to wrap it in a Specifically with IntelliJ, I can just click on the function and be automatically brought to the UnitTest, which shows me how this function is intended to be called programmatically. I can then run that specific test or function as needed to verify that function is working like it's supposed to. I can even make small changes or tweaks to validate that something is working the way I think it is. This is not an option if no tests exist and a separate binary has to be used to test them.
A similar issue was raised in the working group. In short, we decided to include the class and imports to avoid pushing completing that boilerplate stuff on the user.
Part of our current sample format dictates that we should minimize parameters to what's absolutely required for testing (which up to this point has been pretty much resource names). Not only was this done with intent to help cap the scope of difficulty to generating tests, but because we wanted to avoid making the assumption that users may already know the best way to read a file or some other task. Ideally, we should be showing users how to initialize these resources as part of it.
This to me means that the snippet is executable, but not copy-paste-runnable. In summary, my concern as a whole is that our focus for the snippets seems to have shifted priority from "teach the user how to do something" to "get the user to do something". I think that making the snippet easy for the user to run is a big part of making it easy to learn from, but don't think that compiling and executing it is quite the same thing. |
|
This discussion is very helpful! It sounds like there are at least the following issues:
|
Great 👍 Here is one of a current canonical samples (plus the update for the overloaded function) for reference. In particular, I think that the noargs function is critical if we decide not to generate tests, because it gives the user a foothold to configure and to run it instead.
I'm okay with it as long as it doesn't effect the sample that's down (like unnecessary imports). If so, I'd prefer we move the
This was originally our intent too, but IIRC, there was a UX study done in the original group (before I joined) that pointed to the vast majority of users discovering and using the code directly from the docs, and preferring not to visit GitHub. |
lesv
left a comment
There was a problem hiding this comment.
Wow - Real progress - I love it.
- I can live w/ just gradle, but I would like to see build.gradle files.
- Don't love the command line, but can live with it.
- don't see how your testing. Early on, we are often the canary in the coal mine for eng.
- try / catch should do something, explain something, be specific, and offer mitigation suggestions.
com.exampleis better thancom.google.cloud.examplesfor package names. See RFC 2606 & 6761
| // sample-metadata: | ||
| // title: Classify Content (GCS) | ||
| // description: Classifying Content in text file stored in Cloud Storage | ||
| // usage: gradle run -PmainClass=com.google.cloud.examples.language.v1.LanguageClassifyGcs [--args='[--gcs_content_uri "gs://cloud-samples-data/language/classify-entertainment.txt"]'] |
There was a problem hiding this comment.
Wow that's a horrifying command line. Great for cutting and pasting, but folks really aren't likely to get it right all that often. Is a gradle.build a part of the generated output?
I'm ok with just gradle, but there needs to be something, and it should be easier than the -PmainClass=... line you have.
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| // DO NOT EDIT! This is a generated sample ("Request", "language_classify_gcs") |
There was a problem hiding this comment.
Perhaps we could use softer language than "DO NOT EDIT!", we expect our users to play with this and at times modify it to their needs. Just striking the DO NOT EDIT! is probably enough.
| import org.apache.commons.cli.Options; | ||
|
|
||
| public class LanguageClassifyGcs { | ||
| // [START language_classify_gcs] |
| /* | ||
| * Please include the following imports to run this sample. | ||
| * | ||
| * import com.google.cloud.language.v1.ClassificationCategory; | ||
| * import com.google.cloud.language.v1.ClassifyTextRequest; | ||
| * import com.google.cloud.language.v1.ClassifyTextResponse; | ||
| * import com.google.cloud.language.v1.Document; | ||
| * import com.google.cloud.language.v1.LanguageServiceClient; | ||
| */ |
There was a problem hiding this comment.
I'd rather this not be in the comment - why do this when it's already defined above?
| * gs://[Your Bucket]/[Path to File] The text file must include at least 20 words. | ||
| */ | ||
| public static void sampleClassifyText(String gcsContentUri) { | ||
| try (LanguageServiceClient languageServiceClient = LanguageServiceClient.create()) { |
There was a problem hiding this comment.
I.e. is this an initialized thing that I can use throughout the life of my app? is it thread-safe? (or do I need one for each thread (ie. request))? Is create an expensive (time consuming) operation?
All these things developers need guidance on.
| @@ -0,0 +1,60 @@ | |||
| --- | |||
There was a problem hiding this comment.
It might be nice to explain this file somewhere. Like in a comment at the top.
| artman_output_name='') | ||
| artman_output_name='', | ||
| include_samples=True) | ||
|
|
There was a problem hiding this comment.
Is this file checked in? Should it be?
| // title: Transcribe Audio File using Long Running Operation (Local File) (LRO) | ||
| // description: Transcribe a long audio file using asynchronous speech recognition | ||
| // usage: gradle run -PmainClass=com.google.cloud.examples.speech.v1.SpeechTranscribeAsync [--args='[--local_file_path "resources/brooklyn_bridge.raw"]'] |
There was a problem hiding this comment.
// This is a generated sample ("LongRunningRequestAsync", "speech_transcribe_async":
//
// Transcribe Audio File using Long Running Operation (Local File) (LRO)
//
// Transcribe a long audio file using asynchronous speech recognition
//
// usage: gradle run -PmainClass=com.example.speech.v1.SpeechTranscribeAsync [--args='[--local_file_path "resources/brooklyn_bridge.raw"]']
| Path path = Paths.get(localFilePath); | ||
| byte[] data = Files.readAllBytes(path); | ||
| ByteString content = ByteString.copyFrom(data); |
There was a problem hiding this comment.
a comment & blank lines talking about reading from a local file (you'll need to adapt for your needs) would be good here.
| Path path = Paths.get(localFilePath); | ||
| byte[] data = Files.readAllBytes(path); | ||
| ByteString content = ByteString.copyFrom(data); |
There was a problem hiding this comment.
bring out local system stuff.
|
@lesv the gradle thing is a bug - there will (most likely) be a maven pom for the sample package, but I forgot to change from gradle to maven. |
This Pull Request contains two code samples which were generated using the GAPIC generator:
language_sentiment_text) (config YAML)speech_transcribe_async) (config YAML)These code samples demonstrate a few features of the code sample generator:
You can find the existing, published hand-written versions of these code samples here:
For Java, existing hand-written code samples can be found in the
java-docs-samplesrepository.The goal is for these generated code samples to meet the expectations of hand-written samples.
Language experts, please TAL! 🧐
/cc @kurtisvg @nnegrey @chingor13 @lesv @averikitsch
/cc @googleapis/samplegen
This is one of seven Pull Requests being sent for code review of rendered generated sample syntax.