• 0 Posts
  • 9 Comments
Joined 1 year ago
cake
Cake day: June 17th, 2023

help-circle

  • Circular dependencies can be removed in almost every case by splitting out a large module into smaller ones and adding an interface or two.

    In your bot example, you have a circular dependency where (for example) the bot needs to read messages, then run a command from a module, which then needs to send messages back.

        v-----------\
      bot    command_foo
        \-----------^
    

    This can be solved by making a command conform to an interface, and shifting the responsibility of registering commands to the code that creates the bot instance.

        main <---
        ^        \
        |          \
        bot ---> command_foo
    

    The bot module would expose the Bot class and a Command instance. The command_foo module would import Bot and export a class implementing Command.

    The main function would import Bot and CommandFoo, and create an instance of the bot with CommandFoo registered:

    // bot module
    export interface Command {
        onRegister(bot: Bot, command: string);
        onCommand(user: User, message: string);
    }
    
    // command_foo module
    import {Bot, Command} from "bot";
    export class CommandFoo implements Command {
        private bot: Bot;
    
        onRegister(bot: Bot, command: string) {
            this.bot = bot;
        }
    
        onCommand(user: User, message: string) {
            this.bot.replyTo(user, "Bar.");
        }
    }
    
    // main
    import {Bot} from "bot";
    import {CommandFoo} from "command_foo";
    
    let bot = new Bot();
    bot.registerCommand("/foo", new CommandFoo());
    bot.start();
    

    It’s a few more lines of code, but it has no circular dependencies, reduced coupling, and more flexibility. It’s easier to write unit tests for, and users are free to extend it with whatever commands they want, without needing to modify the bot module to add them.


  • eth0p@iusearchlinux.fyitoAsklemmy@lemmy.ml*Permanently Deleted*
    link
    fedilink
    English
    arrow-up
    7
    ·
    edit-2
    1 year ago

    A couple years back, I had some fun proof-of-concepting the terrible UX of preventing password managers or pasting passwords.

    It can get so much worse than just an alert() when right-clicking.

    The codepen.

    A small note: It doesn’t work with mobile virtual keyboards, since they don’t send keystrokes. Maybe that’s a bug, or maybe it’s a security feature ;)

    But yeah, best tried with a laptop or desktop computer.

    How it detects password managers:

    • Unexpected CSS or DOM changes to the input element, such as an icon overlay for LastPass.

    • Paste event listening.

    • Right clicking.

    • Detecting if more than one character is inserted or deleted at a time.

    In hindsight, it could be even worse by using Object.defineProperty to check if the value property is manipulated or if setAttribute is called with the value attribute.


  • This may be an unpopular opinion, but I like some of the ideas behind functional programming.

    An excellent example would be where you have a stream of data that you need to process. With streams, filters, maps, and (to a lesser extent) reduction functions, you’re encouraged to write maintainable code. As long as everything isn’t horribly coupled and lambdas are replaced with named functions, you end up with a nicely readable pipeline that describes what happens at each stage. Having a bunch of smaller functions is great for unit testing, too!

    But in Java… yeah, no. Java, the JVM and Java bytecode is not optimized for that style of programming.

    As far as the language itself goes, the lack of suffix functions hurts readability. If we have code to do some specific, common operation over streams, we’re stuck with nesting. For instance,

    var result = sortAndSumEveryNthValue(2, 
                     data.stream()
                         .map(parseData)
                         .filter(ParsedData::isValid)
                         .map(ParsedData::getValue)
                    )
                    .map(value -> value / 2)
                    ...
    

    That would be much easier to read at a glance if we had a pipeline operator or something like Kotlin extension functions.

    var result = data.stream()
                    .map(parseData)
                    .filter(ParsedData::isValid)
                    .map(ParsedData::getValue)
                    .sortAndSumEveryNthValue(2) // suffix form
                    .map(value -> value / 2)
                    ...
    

    Even JavaScript added a pipeline operator to solve this kind of nesting problem.

    And then we have the issues caused by the implementation of the language. Everything except primitives are an object, and only objects can be passed into generic functions.

    Lambda functions? Short-lived instances of anonymous classes that implement some interface.

    Generics over a primitive type (e.g. HashMap<Integer, String>)? Short-lived boxed primitives that automatically desugar to the primitive type.

    If I wanted my functional code to be as fast as writing everything in an imperative style, I would have to trust that the JIT performs appropriate optimizations. Unfortunately, I don’t. There’s a lot that needs to be optimized:

    • Inlining lambdas and small functions.
    • Recognizing boxed primitives and replacing them with raw primitives.
    • Escape analysis and avoiding heap memory allocations for temporary objects.
    • Avoiding unnecessary copying by constructing object fields in-place.
    • Converting the stream to a loop.

    I’m sure some of those are implemented, but as far as benchmarks have shown, Streams are still slower in Java 17. That’s not to say that Java’s functional programming APIs should be avoided at all costs—that’s premature optimization. But in hot loops or places where performance is critical, they are not the optimal choice.

    Outside of Java but still within the JVM ecosystem, Kotlin actually has the capability to inline functions passed to higher-order functions at compile time.

    /rant


  • Yep! I ended up doing my entire co-op with them, and it meshed really well with my interest in creating developer-focused tooling and automation.

    Unfortunately I didn’t have the time to make the necessary changes and get approval from legal to open-source it, but I spent a good few months creating a tool for validating constraints for deployments on a Kubernetes cluster. It basically lets the operations team specify rules to check deployments for footguns that affect the cluster health, and then can be run by the dev-ops teams locally or as a Kubernetes operator (a daemon service running on the cluster) that will spam a Slack channel if a team deploys something super dangerous.

    The neat part was that the constraint checking logic was extremely powerful, completely customizable, versioned, and used a declarative policy language instead of a scripting language. None of the rules were hard-coded into the binary, and teams could even write their own rules to help them avoid past deployment issues. It handled iterating over arbitrary-sized lists, and even could access values across different files in the deployment to check complex constraints like some value in one manifest didn’t exceed a value declared in some other manifest.

    I’m not sure if a new tool has come along to fill the niche that mine did, but at the time, the others all had their own issues that failed to meet the needs I was trying to satisfy (e.g. hard-coded, used JavaScript, couldn’t handle loops, couldn’t check across file boundaries, etc.).

    It’s probably one of the tools I’m most proud of, honestly. I just wish I wrote the code better. Did not have much experience with Go at the time, and I really could have done a better job structuring the packages to have fewer layers of nested dependencies.


  • Back when I was in school, we had typing classes. I’m not sure if that’s because I’m younger than you and they assumed we has basic computer literacy, or older than you and they assumed we couldn’t type at all. In either case, we used Macs.

    It wasn’t until university that we even had an option to use Linux on school computers, and that’s only because they have a big CS program. They’re also heavily locked-down Ubuntu instances that re-image the drive on boot, so it’s not like we could tinker much or learn how to install anything.

    Unfortunately—at least in North America—you really have to go out of your way to learn how to do things in Linux. That’s just something most people don’t have the time for, and there’s not much incentive driving people to switch.


    A small side note: I’m pretty thankful for Valve and the Steam Deck. I feel like it’s been doing a pretty good job teaching people how to approach Linux.

    By going for a polished console-like experience with game mode by default, people are shown that Linux isn’t a big, scary mish-mash of terminal windows and obscure FOSS programs without a consistent design language. And by also making it possible to enter a desktop environment and plug in a keyboard and mouse, people can* explore a more conventional Linux graphical environment if they’re comfortable trying that.