github.com/google/osv-scalibr@v0.4.1/docs/style_guide.md (about)

     1  # OSV-SCALIBR style guide
     2  
     3  OSV-SCALIBR, like most of Google's Go projects, follows the [Google Go style
     4  guide](https://google.github.io/styleguide/go). Apart from this, we have some
     5  specific OSV-SCALIBR specific best practices:
     6  
     7  ## Code structure
     8  
     9  ### Line length
    10  
    11  Use 80 characters per line when possible. Exceptions apply if splitting
    12  something into several lines hurts readability.
    13  
    14  ### Short functions
    15  
    16  Prefer short functions. If a function is getting too large, consider moving
    17  self-contained parts of the business logic into separate private helper
    18  functions with descriptive names, even if they're only called once.
    19  
    20  Similarly, if the list of params to a function is getting too long, consider
    21  moving them to an [option
    22  structure](https://google.github.io/styleguide/go/best-practices.html#option-structure).
    23  This shortens the function definitions and makes it easier to oversee the
    24  purpose of each param.
    25  
    26  ### Function ordering: General to specific
    27  
    28  Define the public structs/functions/etc. in a class first, then define the
    29  private functions.
    30  
    31  Within private functions, define the higher-order ones first. E.g. if function
    32  `a()` is calling `b()`, define `a` first, then `b`.
    33  
    34  ### Short inline functions
    35  
    36  Keep inline functions short. If they're getting long, prefer moving them into a
    37  separate named private function.
    38  
    39  Since inline functions can capture vars from the parent function they can get
    40  hard to oversee if they grow too large. By factoring them into separate
    41  functions, any vars from the parent function have to be passed as parameters,
    42  making it easier to understand how the function affects the surrounding code.
    43  
    44  ### Inline constants
    45  
    46  If a constant is private and used only once, prefer inlining it where it's used
    47  instead of adding a top-level const declaration.
    48  
    49  Regexes are exempt from this since they should be initialized at startup - See
    50  the section below for more details.
    51  
    52  ### Avoid init()
    53  
    54  Usage of `init()` makes it hard to keep track of the control flow. Prefer
    55  avoiding it in production code. In tests you can use `TestMain()` instead.
    56  
    57  ### Context propagation
    58  
    59  OSV-SCALIBR library users can pass in a `context.Context` to control timeouts
    60  and context cancellation. Make sure this top-level context is passed down to
    61  lower functions (don't initialize a new context with `context.Background()`) and
    62  check for context cancellation whenever something long-running is performed such
    63  as looping
    64  ([example](https://github.com/google/osv-scalibr/blob/8b03d0859edf445152f34c420f50ffe0abf057df/extractor/filesystem/os/dpkg/dpkg.go#L183)).
    65  
    66  ## Error handling
    67  
    68  ### Avoid panics
    69  
    70  If a plugin encounters an error the rest of OSV-SCALIBR and the callers' code
    71  shouldn't crash. Avoid calling panics and prefer propagating errors instead.
    72  
    73  ### Init regexes at startup time
    74  
    75  Add all regex definitions that use `MustCompile` as global vars that initialize
    76  at startup time
    77  ([example](https://github.com/google/osv-scalibr/blob/8b03d0859edf445152f34c420f50ffe0abf057df/extractor/filesystem/os/nix/nix.go#L92)).
    78  This allows the initialization computation to be done up front and catches any
    79  potential crashes before the scan runs.
    80  
    81  ### Propagate or log errors
    82  
    83  In general, propagate errors upwards to the caller.
    84  
    85  If the error is expected or not something that should make the module fail (e.g.
    86  an Extractor encountered an invalid package.json file) there's no need to
    87  propagate it but consider logging a warning instead.
    88  
    89  ## Testing
    90  
    91  ### Don't use `t.Parallel()`
    92  
    93  While `t.Parallel()` allows tests to run faster, they cause test logs in our
    94  internal systems to be mixed together for various test cases, making them harder
    95  to read. OSV-SCALIBR unit tests also only take a couple of seconds to run so
    96  there's not much benefit in adding `t.Parallel()` at the moment.
    97  
    98  ### Avoid assertion libraries
    99  
   100  Generally avoid creating helper libraries that [perform test
   101  assertions](https://google.github.io/styleguide/go/decisions.html#assertion-libraries).
   102  Instead, use helper libs to transform your data into a more easily comparable
   103  structure and perform the comparisions/assertions in the main test function.
   104  Example: the
   105  [extracttest](https://github.com/google/osv-scalibr/blob/8b03d0859edf445152f34c420f50ffe0abf057df/extractor/filesystem/language/dart/pubspec/pubspec_test.go#L296)
   106  helper lib.
   107  
   108  An exception is when the helper library is used to set up the testing
   109  environment (e.g. create specific files). In these cases it's fine to assert
   110  that the setup succeeded in the library function as long as the setup code is
   111  not related to the functionality being tested
   112  ([example](https://github.com/google/osv-scalibr/blob/8b03d0859edf445152f34c420f50ffe0abf057df/extractor/filesystem/os/dpkg/dpkg_test.go#L1527)).
   113  
   114  ### Use easy to find subtest descriptions
   115  
   116  Use only alphanumeric characters and underscores in test descriptions. Don't use
   117  spaces. Test logs transform these descriptions by substituting the spaces which
   118  makes the failing tests from the logs harder to find in the code
   119  ([example](/binary/cli/cli_test.go#L258;rcl=732940634)).
   120  
   121  ### Test for multi-platform support
   122  
   123  OSV-SCALIBR runs on Linux, Windows, and Mac. When adding new code, make sure
   124  your code is compatible with all 3 OSes or that you're adding a component that's
   125  only meant to run on a given OS. Check that the SCALIBR Github Actions for all 3
   126  OSes pass.
   127  
   128  When using OS specific helper libraries consider adding dummy implementations
   129  for other OSes
   130  ([example](https://github.com/google/osv-scalibr/blob/main/extractor/standalone/windows/ospackages/ospackages_dummy.go)).
   131  
   132  One common change that fails on Window is introducing file path processing code
   133  that uses the wrong kinds of slashes (`/` vs `\`). When dealing with absolute
   134  paths, use built-in functions such as `filepath.Join()` to handle path
   135  operations. Virtual paths use the `fs.FS` interface which uses `/` even on
   136  Windows. In these cases you can sanitize your paths with `filepath.ToSlash`
   137  ([example](https://github.com/google/osv-scalibr/blob/daa1498e42aafe6a9258df854cb3bfee17b6808b/extractor/filesystem/language/python/requirements/requirements.go#L193)).
   138  
   139  ## Performance
   140  
   141  OSV-SCALIBR is meant to also run on systems with constrained resources and new
   142  code should thus try to keep its runtime and resource usage low. Plugins that
   143  have a high resource consumption will be able to run in less contexts and will
   144  thus be less useful.
   145  
   146  ### Avoid expensive operations in `FileRequired`
   147  
   148  Extractor plugins' `FileRequired()` function can get called on every file on the
   149  scanned filesystem. Keep the checks simple by using simple string comparison
   150  logic. Define the checks inside `FileRequired()` instead of separate functions
   151  as function calls can add additional runtime overhead.
   152  
   153  Avoid doing expensive file path comparisons such as regexp matching unless
   154  you've already pre-filtered the files and can be sure that the more expensive
   155  operations will only run on a small subset of the files.
   156  
   157  ### Avoid reading full binaries into memory
   158  
   159  When parsing binaries and lockfiles that can get large, avoid reading all of the
   160  file contents into memory whenever possible. Prefer to use streaming readers.
   161  For reading a specific section of a large file, prefer using
   162  [`ReadAt()`](https://pkg.go.dev/io#ReaderAt) instead of slicing out the relevant
   163  sections in memory.
   164  
   165  ## Miscellaneous
   166  
   167  ### Use the Unit lib for large numbers
   168  
   169  OSV-SCALIBR has a
   170  [unit lib](https://github.com/google/osv-scalibr/blob/main/extractor/filesystem/internal/units/units.go)
   171  for commonly used data size units. Use the values from these lib instead code
   172  like `"2 * 1024 * 1024"`.
   173  
   174  ### Prefer %q over %s
   175  
   176  When formatting strings, `%q` adds escapes and quotation marks and makes it
   177  easier to see where a string variable in the log message starts. It also makes
   178  it easier to see empty strings in logs
   179  ([example](https://github.com/google/osv-scalibr/blob/daa1498e42aafe6a9258df854cb3bfee17b6808b/scalibr.go#L118)).
   180  
   181  ### Add docstrings to public functions and types
   182  
   183  All public functions and type should have [doc
   184  comments](https://tip.golang.org/doc/comment).