Updates from kmitov Toggle Comment Threads | Keyboard Shortcuts

  • kmitov 6:44 am on March 30, 2021 Permalink |
    Tags: , , , , , , ,   

    Dead code and one more way it could hit you back – by being loaded! 

    (Everyday Code – instead of keeping our knowledge in a README.md let’s share it with the internet)

    We can all agree that dead code should be removed. What we sometimes fail to see is how much it could cost to leave dead code with our product. Today’s article is about an example of a “dead code” and how it hit us back 1.5 years after we stopped using it. It took us 3 hours to debug and find the root cause. The conclusion is that we should have done ‘git rm’ an year ago. We did not and the cost was 3 hours.

    This is a real life example article from our code base and it is design to share experience with our team, but I hope the whole community could benefit.

    What happened

    1. Jenkins build failed

    First time we saw it we identified that it is not something serious and it kept failing for a few days until we had time to address it in the next Sprint.

    2. Teaspoon, Jasmine, Bundler, Rails Engines

    In the project there was the production code in Rails.root along with a dummy test code for starting JavaScript specs in Rails.root/test/dummy/. We are using Teaspoon with Jasmine in a Rails engines. Rails engines tend to create a test/dummy folder in which you could put the code for a test app in which you want your JavaScript specs to be executed. The product code is loaded in the test app and specs are executed.

    About 1.5 years ago I did some experiments with this test app and made a few configurations. The goal was to have the production code along with some test app code both loaded in the test environment. In this way Teaspoon and Jasmine would see both the production and the test app code as coming from “production”. I literally remembered what my idea was and what I was trying to achieve. At the end I decided not to use the test app code, but to have only the production code located in Rails.root be loaded in the test environment.

    But I forgot to remove the test app code from test/dummy/app/assets/javascripts/

    3. Teaspoon Error

    The following error occurred. Here “IS” is a JavaScript namespace part of the production code.

    jenkins@vpszap6s:~/jobs/is-core Build and Release/workspace/test/dummy$ xvfb-run -a bundle exec rake teaspoon --verbose
    Starting the Teaspoon server...
    Teaspoon running default suite at http://127.0.0.1:36051/teaspoon/default
    ReferenceError: IS is not defined
    
    

    4. The error and debugging process

    Not sure what the error was exactly I tried to identify the difference between my machine and the server machine where the tests was failing. This server machine is called “elvis”.

    1. Bundler version – my version was 2.2.11 and the elvis version was 2.2.14. Tried with upgrade, but it did not resolve the issue.
    2. Chrome driver – since the tests were executed on chrome I saw the chrome driver versions were different. Mine was 86. elvis was with 89. Synced them, the error was still occurring.
    3. Rake version – rake versions were the same
    4. Ruby version – the same
    5. Teaspoon and Jasmine versions – the same
    6. The OS is the same
    7. I could not find any difference between my env and the elvis env.

    Turns out the problem was in the loading order. elvis was loading test app code and then production code. My machine was loading production code and then test app code. I could not figure out why. The whole test app code was:

    // test/dummy/app/assets/javascripts/ext/dummy/dummy.js 
    IS.Dummy = {};

    The error that was occurring was:

    ReferenceError: IS is not defined

    5. Solution

    I was curious to continue debugging to see why the two machines were loading the code in a different order, but at the end decided against it. Just removed the dead code in test/dummy/app/assets/javascripts/ext/dummy/dummy.js which I only thought as dead, but it turned out it was affecting our project.

    6. Builds passing

    Finally the builds were passing.

    Conclusion

    Dead code my not be that dead after all. It might be loaded even if not used and loading order could differ. Better be safe and remove dead code at all. If we need it that much, we have it in the GIT repo history.

     
  • kmitov 6:46 am on March 26, 2021 Permalink |
    Tags: iptables, , ufw   

    ufw is not blocking traffic the way you think it is? 

    (Everyday DevOps and Infrastructure – instead of keeping the knowledge in our team, let’s share it with the community)

    Today we again got reminded that what you’ve configured in ufw is not what actually happening.

    ufw is wrapping iptables. The rules that you seen in ufw are not really the rules that are applied for the firewall. They could be different. They could be a lot different.

    To get the complete rules you should check

    $ iptables --list

    Then you should flush the iptables and start from zero and recreate the rules of ufw. Of course, backup the rules if anything goes wrong, but ufw is not the actually firewall. It just helps you manage your iptables.

    I think there should be some kind of notification when opening ufw status where it should tell you that iptables rules are different from the ufw status rules. This could increase quality of life and general security on the internet.

     
  • kmitov 6:32 am on March 26, 2021 Permalink |
    Tags: ,   

    libssl.so.1.0.0: cannot open shared object file: 

    (Everyday DevOps and Infrastructure – instead of keeping the knowledge insider of our organization let’s share it with the world)

    We: – Today it will be a great day

    The god of computers: – I don’t think so. Here is something for you

    App 1558233 output: Error: The application encountered the following error: libssl.so.1.0.0: cannot open shared object file: No such file or
     directory - /usr/....path.../x86_64-linux/openssl.so (LoadError)

    Why it happened?

    We did

    $ sudo apt get upgrade 
    $ suod autoremove

    Nothing of importance to see there and we went about with doing the autorremove. But it turns out autoremove has removed libssl. Why? I don’t know. I guess that because it has 1.1 it will automatically remove 1.0.0. But

    How we fixed it?

    # As we can see there is not libssl.so.1.0.0
    kmitov@vpszap6s:/usr$ sudo find . -name libssl.so*
    ./lib/x86_64-linux-gnu/libssl.so
    ./lib/x86_64-linux-gnu/libssl.so.1.1
    
    # So we install it
    $ sudo apt-get install libssl1.0.0
    
    # And now there is
    $ sudo find . -name libssl.so*
    ./lib/x86_64-linux-gnu/libssl.so
    ./lib/x86_64-linux-gnu/libssl.so.1.0.0
    ./lib/x86_64-linux-gnu/libssl.so.1.1

     
  • kmitov 1:34 pm on March 25, 2021 Permalink |
    Tags: , , ,   

    Stimulus 1.1.1 to Stimulus 2.0.0 – practical cost 

    We just migrated Stimulus 1.1.1. to Stimulus 2.0.0 so I decided to share this with our whole team, but I thought this could be useful for the whole community.

    The practical cost is that this migration could be done in a few minutes per Stimulus controller. 10-20 controllers – you should be done in less than a day.

    Overview of the changes

    Here are a few examples of the changes that were committed for a single _form.html.erb

    html.erb

    # app/views/public_users_searches/_form.html.erb
    -            target: "public-users-searches.term",
    +            public_users_searches_target: "term",
    ...
    -    <%= f.submit t('public_users_searches.form.search'), data: {target: "public-users-searches.commit", value: t('public_users_searches.form.search')} do %>
    +    <%= f.submit t('public_users_searches.form.search'), data: {public_users_searches_target: "commit", value: t('public_users_searches.form.search')} do %>
    

    Conclusion

    If you are wondering whether you should migrate or not and how much it will cost probably you should migrate. It is not that expensive.

     
  • kmitov 6:44 am on March 25, 2021 Permalink |
    Tags: , ,   

    A practical example for dependency injection 

    In today’s article I am sharing with our team an example I found while reading parts of the code in one of our platforms where we’ve created a form of ‘cyclic’ references with cancancan merges in Rails Controllers. I wondered for a while how we’ve managed to create it, how to avoid creating them in the future and I share this as an example with our team, but I hope the article could be useful for the whole community.

    I see this as a perfect example for the use of Dependency Injection.

    The example

    CourseSectionsAbilities is merged with ContentsAbility which is merged with ContentRefsAbilities which is then merged with CourseSectionAbilities. This means that CourseSectionsAbilities is merged twice and the second merge overrides the first merge.

    class CourseSectionsController < CommonController
    
      def current_ability
        # This is the first merge 
        # CoruseSectionsAbility is merged with ContentsAbility.
        @current_ability ||= Abilities::CourseSectionsAbility.new(current_user)
                               .merge(Abilities::ContentsAbility.new(current_user))
      end
    
    end
    
    module Abilities
      class ContentsAbility
        include CanCan::Ability
    
        def initialize user
          ...
          # This is the second merge
          # ContentsAbility is merged with ContentRefs ability
          merge ContentRefsAbility.new user
        end
      end
    end
    
    module Abilities
    
      class ContentRefsAbility
        include CanCan::Ability
    
        def initialize user
          # This is the third merge
          # ContentRefsAbility is merged with CourseSectionsAbilily.
          # This overrides the first merge.
          merge CourseSectionsAbility.new(user)
          ...
        end
      end
    end

    How did it happen?

    It think it is the classic grow of the code where you solve the problem in the easiest way possible. Initially we had:

    class CourseSectionsController < CommonController
    
      def current_ability
        # This is the first merge 
        # CoruseSectionsAbility is merged with ContentsAbility.
        @current_ability ||= Abilities::CourseSectionsAbility.new(current_user)
                               .merge(Abilities::ContentsAbility.new(current_user))
      end
    
    end

    But then a new requirement about an year after that has come and there is a commit that adds the second merge:

    module Abilities
    
      class ContentRefsAbility
        include CanCan::Ability
    
        def initialize user
          # This is the third merge
          # ContentRefsAbility is merged with CourseSectionsAbilily.
          # This overrides the first merge.
          merge CourseSectionsAbility.new(user)
          ...
        end
      end
    end

    What is the problem?

    The problem is that there is a class called ContentRefsAbility that can dependent on everything it wants. It can merge anything inside it with any consideration on what was already merged. It can set it’s own dependencies. This couples the ContentRefsAbility with the places it is used. Because we must take into consideration every place where ContentRefsAbility is used before changing it’s implementation.

    How Dependency Injection solves this?

    We pass the ability in the constructor

    module Abilities
    
      class ContentRefsAbility
        include CanCan::Ability
    
        def initialize user, outside_ability
          # This is the third merge
          # ContentRefsAbility is merged with CourseSectionsAbilily.
          # This overrides the first merge.
          outside_ability.method1 # we call the method of the outside_ability
          ...
        end
      end
    end

    Instead of creating the ability in the class we pass the dependency from the outside. In this way we can control the dependency and choose different dependencies in different conditions.

    The ContentRefsAbility no longer depends on the specific implementation of outside ability, but it depends on the behavior we inject from the outside.

     
  • kmitov 7:52 am on March 24, 2021 Permalink |
    Tags: , , , , ,   

    We don’t need ‘therubyracer’ and the libv8 error for compiling native extensions 

    This article is about the error:

    Installing libv8 3.16.14.19 with native extensions
    Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    and the fact that we don’t need and probably you don’t need therubyracer.

    There are two solutions for the above error:

    # To install libv8 with a V8 without precompiling V8 
    $ gem install libv8 -v '3.16.14.19' -- --with-system-v8
    
    # Or to comment therubyracer in your Gemfile and not to use it.
    # gem 'therubyracer'

    We went for the later. Here are more details

    What is libv8 and therubyracer

    From libv8

    V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++. It is used in Chrome and in Node.js, among others.

    What therubyracer does is to embeds the V8 Javascript Interpreter into ruby.

    In Rails there is the execjs gem used in assets precompilations.

    ExecJS lets you run JavaScript code from Ruby. It automatically picks the best runtime available to evaluate your JavaScript program, then returns the result to you as a Ruby object.

    https://github.com/sstephenson/execjs

    This means that execjs needs a JS runtime and it uses the one provided by therubyracer, libv8 and V8. But it can use other runtimes as well, like Node.

    What is the libv8 compilation error?

    The libv8 error is because it can not compile V8 on the machine. V8 is developed in C++. To successfully compile it the machine must have all the dependencies needed for compilation. You can try to compile it from scratch or you could use an already compiled one. Compiling from scratch takes time to resolve all the dependencies.

    But we didn’t need therubyracer

    Turns out that in this specific platform we don’t need therubyracer, because we already have Node because of webpacker/webpack and we have it on all the machines and we have it on production on Heroku.

    therubyracer is also discouraged on Heroku –

    If you were previously using therubyracer or therubyracer-heroku, these gems are no longer required and strongly discouraged as these gems use a very large amount of memory.

    A version of Node is installed by the Ruby buildpack that will be used to compile your assets.

    https://devcenter.heroku.com/articles/rails-asset-pipeline#therubyracer

    therubyracer is great for starting quickly especially when there is no node. But considering there there was node on all of our machines we just don’t need therubyracer.

    As a result the slug size on heroku was reduced from 210M to 195.9M. No bad. I guess the memory consumption will also be reduced.

    remote: -----> Compressing...        
    remote:        Done: 210M        
    remote: -----> Launching...        
    remote:        Released v5616     
    
    remote: -----> Compressing...        
    remote:        Done: 195.9M        
    remote: -----> Launching...        
    remote:        Released v5617       

    How did we came about this error?

    Every Wednesday we do an automatic bundle update to see what’s new and update all the gems (of course if the tests pass). This Wednesday the build failed, because of the libv8 error and native compilation.

    It was just an unused dependency left in the Gemfile.

     
  • kmitov 7:03 am on March 1, 2021 Permalink |
    Tags: , ,   

    JSON.parse(“1”) works. JSON.parse(“k1”) throws an exception 

    (Everyday Code – instead of keeping our knowledge in a README.md let’s share it with the internet)

    TL; DR; – JSON.parse(“1”) works. JSON.parse(“k1”) throws an exception

    This is a valid JSON

    "1"

    This is NOT a valid JSON

    "k1"

    The code

    $ irb
    2.6.5 :001 > require 'json'
     => true 
    2.6.5 :002 > JSON.parse("1")
     => 1 
    2.6.5 :003 > JSON.parse("k1")
    Traceback (most recent call last):
            6: from /home/kireto/.rvm/rubies/ruby-2.6.5/bin/irb:23:in `<main>'
            5: from /home/kireto/.rvm/rubies/ruby-2.6.5/bin/irb:23:in `load'
            4: from /home/kireto/.rvm/rubies/ruby-2.6.5/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
            3: from (irb):3
            2: from /home/kireto/.rvm/gems/ruby-2.6.5/gems/json-2.5.1/lib/json/common.rb:216:in `parse'
            1: from /home/kireto/.rvm/gems/ruby-2.6.5/gems/json-2.5.1/lib/json/common.rb:216:in `parse'
    JSON::ParserError (809: unexpected token at 'k1')
    2.6.5 :004 > JSON.parse("\"1\"")
    

    It is always mistakes as simple as that, that are causing the bugs.

    Details

    I am sharing the details mostly for our team.

    When users use the Instructions Steps framework on the BuildIn3D and FLLCasts platforms we have a communication between the client and the server. The client sends information as JSON, the server parses it. For a few days some of the communication was missed. Not everything was recorded on the server. This was not anything critical, but about 360,000 events were not recorded and it shows how we did not understand JSON.parse entirely.

    The commit

    The commit to cause this was

           else
    +        data = JSON.parse(events_array[0]["data"])
    +        referrer = data["referrer"] if data.is_a? Hash
    +
    

    We get a string from events_array[0][“data”] and we parse this string. But this string is not always a JSON. It is sometimes just pure string. All the specs pass and we are committing the code.

    In the specs suite all the data that we are testing is “1”,”,2″- generally numbers. Why? Because this makes sense looking at the data of the spec and in the spec we’ve had only numbers as the string, but not a general string like “k1”.

    It bites. It bites like a shark.

    FabBRIX WWF, Shark in 3D building instructions
     
  • kmitov 7:37 am on February 19, 2021 Permalink |
    Tags: , typescript   

    “In one paragraph or less” – why I chose JavaScript over TypeScript 

    My idea with this article is to try to summarize and share “in one paragraph or less” the main reason why, as a CTO, I chose one technology over another. We take everything into account, infrastructure, team, business requirements, and many others, which could be quite complex, but can we share the essence.

    In one paragraph or less

    “I chose vanilla JavaScript compiled with Google Closure Compiler and not TypeScript because I was building an extensible plugin based framework and I wanted to allow each and every plugin developer to be free to choose vanilla JavaScript or TypeScript for their plugins. I did not want to limit anyone’s future choices.”

    The pre-story (if you are interested)

    A few days ago I called an old friend. We’ve been acting like CTOs of two companies for a couple of years (I was more acting, he was doing like the real deal). But we haven’t talked in a while. The conversation went pretty fast from “How are things at work and at home?” to “Why did you choose this technology over that technology?”

    He: – We made some interesting things on the technology front.

    Me: – Really? What?

    He: – We went for the React, TypeScript path and did…

    Me: – When I had to make this decision I stayed on the Rails, Stimulus with vanilla JS path.

    He: – You know, if it wasn’t for this and that, I would have done as you did. But you should definitely check GraphQL in more detail.

    Me: – Oh, I have and …

    This conversation went on for some time.

    I had a very similar conversions about an year ago with another CTO friend that said:

    I chose “technology X” instead of “technology Y” to keep some sanity in our team.

    So we make these decisions, but can we communicate them?

     
  • kmitov 7:36 pm on February 15, 2021 Permalink |
    Tags: , , ,   

    Usage of ActiveRecord::Relation#missing/associated 

    Today I learned that Rails 6.1 adds query method associated to check for the association presence. Good to know. I share it with the team at Axlessoft along with the community as a whole.

    While reading the article about the methods I thought:

    Are we going to use these methods in our code base?

    I did some greps and it turns out we won’t.

    $ git grep -A 10 joins | grep where.not | grep nil | wc -l
    0
    

    There is not a single place in our code that we call

    .joins(:association).where.not(association: {id: nil}
    
    or
    
    .joins(:association).where(association: {id: nil})

    What does this mean?

    Nothing actually. Great methods. I was just curious to see if we will be using them in our code base a lot.

     
  • kmitov 6:39 pm on February 15, 2021 Permalink |
    Tags: ,   

    “if statements” are a “code smell” 

    (Everyday Code – instead of keeping our knowledge in a README.md let’s share it with the internet)

    Today we made a code review on a feature. Generally the review process in our team is – someone drops a message in the slack channel and says – “hey, I have these changes. Can you please take a look at them?”. We do not require a merge from an “authority” to get something to production. We also have one rule we are trying to follow:

    Your commit should make the product better than it previously was.

    https://www.axlessoft.com/careers/

    The commit below did not make the code better and this is the article about it. I am sure it would be useful for all of our team members and I hope it will be useful for the community as a whole.

    How “if statements” are a sign of “code smell”

    This is the commit. Do you see the problem with it?

       /**
        * @private
        * @param      {IS.StepsTree.LoadedEvent} event
        */
       onStepsTreeLoaded(event) {
    -    this._buildId = this.generateBuildId();
    +    if (!this._modeChangeOccured) {
    +      this._buildId = this.generateBuildId();
    +    } else {
    +      this._modeChangeOccured = false;
    +    }
    +
    

    The logic in the onStepsTreeLoaded method has significantly changed. It was a simple initialization of a private variable. Now it is an if statement with an else that sets the variable used in the if to a false value.

    Wow. This is even difficult to explain.

    Why was the change introduced and how the “code smell” helped us improve?

    The thing is that our colleague had to do this change to keep the behavior of the code based on a commit from 7 months ago. But now we see this smelly code and we thought:

    Why is this even needed in the first place? Why do we call this onStepsTreeLoaded method and what is it doing for us?

    Turns out that we can just move the initialization from onStepsTreeLoaded method to another method called at a different place and we can delete this method. We would keep the same behavior. There will be no regressions. The framework has changed in the last few months to the point that there is now a better place for this initialization to happen.

    My point is: “if statement”==”code smell”

    Adding an if statement to a working code is probably a code smell. Wrapping an existing code in an if statement based on unrelated state with an else that sets this same state is probably the precise definition of what “code smell” is.

    Conclusion

    Revise your assumptions. Don’t add the if statements. Think again if this is really needed, why it is needed and where is its place in the architecture of the platform.

    The concerned emotion

    Can’t think of a better way to show you the emotion of code smell than to show you this Gorilla.

    FabBRIX WWF, Gorilla in 3D building instructions
     
c
compose new post
j
next post/next comment
k
previous post/previous comment
r
reply
e
edit
o
show/hide comments
t
go to top
l
go to login
h
show/hide help
shift + esc
cancel