Blog Home

Separation of Concerns? That's a Wrap!

2020-12-09

Combining domain logic with third party API calls makes code that is harder to understand especially if the API details creep outside of the call site.

Bad

absl::StatusOr<speedy_img::Image> GetThumbnails(
    absl::Span<const speedy_img::Decoder> decoders,
    absl::Span<const std::byte> data) {
  const speedy_img::Options options = GetDefaultConvertOptions();

  for (const speedy_img::Decoder decoder : decoders) {
    const speedy_img::Result decode_result =
        decoder.Decode(decoder.FormatBytes(data));
    speedy_img::Image image = decode_result.GetImage(options);
    
    absl::Status result = ValidateGoodImage(image);
    if (result.ok()) {
      return absl::StatusOr<speedy_img::Image>(std::move(image));
    }
  }

  return absl::InvalidArgumentError("unable to decode image with any decoder");
}

Encapsulate the external API in a wrapper with a readable interface that insulates the API from the rest of the codebase.

Good

absl::StatusOr<Image> GetThumbnails(absl::Span<const ImageDecoder> decoders,
                                    absl::Span<const std::byte> data) {
  for (const ImageDecoder decoder : decoders) {
    absl::StatusOr<Image> result = decoder.Decode(data);
    if (result.ok()) {
      return result;
    }
  }

  return absl::InvalidArgumentError("unable to decode image with any decoder");
}

Note

Not all external APIs need to be wrapped. For example, the absl library provides fundamental library types; wrapping the API would not make a clear improvement to the code.