sqids / sqids-dotnet

Official .NET port of Sqids. Generate short unique IDs from numbers.

Home Page:https://sqids.org/dotnet

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Suggestions on `SqidsEncoder`

LeaFrock opened this issue · comments

Now the SqidsEncoder has two versions, non-generic and generic. Both of their codes are in one file with a lot of conditional compilation. It's a bit messy to read.

My suggestions are that,

  • Create a new file for SqidsEncoder<T>, for example, SqidsEncoder{T}.cs. So this file is totally for generic version with only one #if NET7_0_OR_GREATER.
  • When the target framework is NET7_0_OR_GREATER, we can still leave a non-generic version and let it derive SqidsEncoder<int>. So, the upgrade from .NET 7 below can be easier and smoother.
  • Create a shared file for reusable codes between two versions, for example, SqidsEncoder.Shared.cs.

I'll do a test first and pull a request when I'm free.

Hi @LeaFrock.

I don't believe this is feasible; separating the generic and non-generic versions of the class means more code duplication, even in places where the code is identical. Using preprocessor directives (e.g. #if NET7_0_OR_GREATER) is simply the best technique we have at our disposal even if it may look somewhat messy. There's also not a lot of code to read anyway, so I don't think that's a problem worth obsessing over.

When the target framework is NET7_0_OR_GREATER, we can still leave a non-generic version and let it derive SqidsEncoder. So, the upgrade from .NET 7 below can be easier and smoother.

I'm not convinced this provides much value, the only thing you need to do to "upgrade" to the .NET 7+ version is add <int> to SqidsEncoder, that's really all there is to it; there's not enough friction in the upgrading process to justify the additional complexity this would bring about.

Create a shared file for reusable codes between two versions, for example, SqidsEncoder.Shared.cs.

I'm not sure how this would even work.

I've already finished my version and hope you take a look if you have free time.

Let me explain more details of what I do.

Separate the generic and non-generic

It doesn't mean more code duplication. And preprocessor directives are still necessary, but in a more read-friendly way. If in a method, we have only several lines targeting to different frameworks while the most lines are the same, preprocessor directives are the best. Otherwise, two methods are better for reading. It might have a little more code lines but just limited to duplicated method signature or '{}'.

The source SqidsEncoder.cs is splitted into 3 parts: SqidsEncoder.cs, SqidsEncoder{T}.cs and SqidsCore.cs.

  • The core implementation (Encode/Decode) is moved into SqidsCore.cs, which is clear to potential contributors. You'll see I keep the preprocessor directives as it's the situation of what I say above: most of code lines can be shared. However, I convert the ToId/ToNumber to static local functions and divide them, because both of them are short and do not share same codes except method signature.

  • SqidsEncoder{T}.cs only targets to .NET 7+, so a developer who only cares about codes related to .NET 6/Standard 2.0 needn't read the file anymore.

  • SqidsEncoder is a special one (and will be mentioned in the following text). All duplicated codes (overloads of Encode/Decode) you think are limited as a skin of API shapes.

Constructor optimization

The logic related to SqidsOption during constructor initialization can be completely encapsulated into the option itself.

As you can see, the internal methods of the option are reusable, and the constructor becomes clearer.

Also, ArgumentNullException.ThrowIfNull is preferred if possible. The encapsulation should bring a bit perf improvement too.

(Optional) Why I hope SqidsEncoder: SqidsEncoder<int>

It's due to my upgrading experience last month. Although the problem is actually that I should read the release note when upgrading any 3rd-party lib, maintaining a certain level of compatibility and making upgrades smoother without incurring excessive costs do help promote the lib.

It will benefit all projects below .NET 7 when upgrading. Upper-level developers can seamlessly upgrade rather than be blocked by IDE or CI/CD errors, then give an emergency fix in a hurry.

Finally, the key point is, it's easy to do it, bringing almost zero maintain cost in the future.

Thanks for your communication!

Thank you very much for your efforts.

But again, I don't really think these changes add significant enough value. I'm a fan of keeping things simple architecturally until the need for a specific design arises. Introducing abstractions/encapsulating things needs to be done only when it's absolutely clear that it's direly needed.

Your SqidsCore class, by the way, is still littered with preprocessor directives; so the problem you originally complained
about doesn't really seem to have been solved.

And I'm not convinced separating things into different files like this is at all preferable to keeping everything in a single place. The library is not complex enough to warrant this sort of elaborate architecture, and virtually no other implementation of Sqids in any language is doing such a thing; it's just unnecessary complexity in my opinion.

Regarding the non-generic SqidsEncoder class for .NET 7+, I understand where you're coming from, but I don't really feel like this is as big a point of concern as you're suggesting. Though I'd be more open to this particular change than the structural one.

Thanks for your reply. I'll close this issue and continue to follow this project 🤝 .