HaifaSoft

Code with Purpose. Solutions with Impact.

Author: Adelin Ghanayem

  • Writing Untestable Code: Lessons from a Google Talk

    Introduction

    In 2008, a Google Talk delved into the intricacies of writing untestable code. While the topic may seem counterintuitive, understanding what makes code difficult to test can guide us toward better programming practices. Here’s a summary of the key points discussed.

    The Intrinsic Knowledge of Writing Hard-to-Test Code

    Many developers, when asked how to write untestable code, struggle to articulate it despite often encountering such code. It’s akin to a spider weaving a web instinctively. Some common characteristics of untestable code include:

    • Making things private
    • Using the final keyword extensively
    • Long, monolithic methods
    • Non-deterministic behavior
    • Mixing business logic with the new operator
    • Performing work in constructors
    • Global state and singletons
    • Static methods

    These practices intertwine dependencies and state, making isolation for testing challenging.

    The Real Issues of Unit Testing

    The heart of the problem lies in separating business logic from object creation and global state. Here are some primary issues:

    • Mixing the new operator with business logic: This practice embeds dependencies directly into your code, making it hard to isolate components for testing.
    • Global state and singletons: These introduce non-deterministic behavior, leading to unpredictable test outcomes.
    • Static methods: While simple leaf static methods like Math.abs() are easy to test, complex ones higher in the call hierarchy become problematic because they lack seams for interception.

    The Challenge of Procedural Programming and Deep Inheritance

    Procedural programming lacks polymorphism, making it hard to isolate code for testing. Deep inheritance hierarchies pose a similar problem; testing a class at the bottom of a hierarchy inadvertently tests all its ancestors.

    The Misconception About Writing Tests

    Contrary to popular belief, there’s no secret sauce to writing tests. The real secret lies in writing testable code. Most people mistakenly assume they can write code first and then test it, but this often results in untestable code.

    Unit Testing as the Solution

    Unit testing should be integrated from the start, with the same developers writing both the code and the tests. This ensures the code is written in a testable manner.

    Levels of Testing

    1. Scenario Tests: Test the whole application as a unit. These tests are slow, flaky, and hard to maintain. They often involve complex setups and numerous variables, leading to unreliable results.
    2. Functional Tests: Test subsystems of the application with simulators for external dependencies. These are more reliable than scenario tests but still not ideal for pinpointing specific issues.
    3. Unit Tests: Test individual classes in isolation. They are fast, reliable, and provide clear feedback on failures. Unit tests should be the foundation of your testing strategy.

    Dependency Injection and Test-Driven Development

    To write testable code, embrace dependency injection. This practice separates object creation from business logic, allowing you to inject dependencies. Test-driven development (TDD) further reinforces this approach, ensuring code is written with testing in mind from the outset.

    Practical Tips for Writing Testable Code

    1. Avoid static methods: They lack seams for interception.
    2. Separate object construction from business logic: Use dependency injection to manage dependencies.
    3. Write small, focused methods: Long methods with multiple responsibilities are hard to test.
    4. Limit global state and singletons: These introduce non-determinism and coupling.
    5. Use interfaces and polymorphism: They provide seams for injecting mocks or stubs.

    Conclusion

    Understanding what makes code hard to test is the first step towards writing testable code. By adopting practices like dependency injection and TDD, and focusing on unit tests, you can ensure your codebase remains maintainable and robust. Remember, the goal is not just to write tests but to write code that can be tested easily and reliably.

  • Angular expressions summary

    1. Square Brackets [] (Property Binding):
    • Used to bind a property of a DOM element to a component property. Example:
       <!-- Binding the 'src' property of an image element to a component property 'imageUrl' -->
       <img [src]="imageUrl">
    1. Parentheses () (Event Binding):
    • Used to bind an event of a DOM element to a component method. Example:
       <!-- Binding the 'click' event of a button to a component method 'handleClick' -->
       <button (click)="handleClick()">
         Click me
       </button>
    1. Double Curly Braces {{ }} (Interpolation):
    • Used to display the value of a component property in the template. Example:
       <!-- Displaying the value of the 'name' property in a paragraph -->
       <p>{{ name }}</p>
    1. Asterisk * (Structural Directives):
    • Used to apply structural directives like ngIf, ngFor, and ngSwitch in a more concise way. Example (ngFor):
       <!-- Looping through an array and rendering items -->
       <div *ngFor="let item of items">
         {{ item }}
       </div>
    1. Hash # (Template Reference Variables):
    • Used to create a reference to an element or component in the template, which can be used in the component code. Example:
       <!-- Creating a reference to an input element -->
       <input #myInput>
    1. Round Brackets () in ngFor and ngIf:
    • Used for local variables when using ngFor and ngIf structural directives. Example (ngFor):
       <!-- Accessing the index of the current item in a loop -->
       <div *ngFor="let item of items; let i = index">
         Item {{ i }}: {{ item }}
       </div>
    1. Pipe | (Pipe Operator):
    • Used for data transformation in templates. It is used to format data before displaying it in the template. Example:
       <!-- Formatting a date before displaying it -->
       <p>{{ currentDate | date:'short' }}</p>
    1. *ngIf and *ngFor (Structural Directives):
    • *ngIf is used for conditionally rendering elements based on a condition. Example (ngIf):
       <!-- Displaying a message only if 'showMessage' is true -->
       <div *ngIf="showMessage">
         This message will be displayed if showMessage is true.
       </div>
    • *ngFor is used for looping through a list of items and rendering elements accordingly. Example (ngFor):
       <!-- Looping through an array of users and rendering their names -->
       <div *ngFor="let user of users">
         {{ user.name }}
       </div>
    1. [()] (Two-Way Binding):
    • Combines property binding and event binding to create a two-way data binding. It’s typically used with ngModel for form elements. Example:
       <!-- Creating a two-way binding for an input element using ngModel -->
       <input [(ngModel)]="myProperty">
    1. [ngStyle] and [ngClass]:
      • Used for dynamically applying styles or classes to elements based on component properties or conditions.
      Example (ngStyle): <!-- Applying a dynamic text color based on the 'textColor' property --> <div [ngStyle]="{ color: textColor }"> This text will have a dynamic color. </div>

    These examples should provide you with a more detailed understanding of how these expressions are used in Angular templates.

  • Public and Private Key, Digital Signatures and HTTPS

    Digital signatures are an essential aspect of modern cryptography and are widely used to guarantee the integrity, authenticity, and non-repudiation of a message or a document. Although there are different algorithms to digitally sign a document, one common method is to use RSA (Rivest-Shamir-Adleman) algorithm.

    Mathematical Background

    Before diving into the steps, let’s understand some of the mathematical elements involved:

    1. Hash Function: A hash function takes an input (or “message”) and returns a fixed-size string, which appears random.
    2. Public Key and Private Key: In RSA, each person has a pair of keys: a public key, which is openly shared, and a private key, which is kept secret.

    RSA Key Generation

    1. Select two large prime numbers, (p) and (q).
    2. Compute (N = p \times q).
    3. Calculate the totient, (\phi(N) = (p-1) \times (q-1)).
    4. Choose an integer (e) such that (1 < e < \phi(N)) and (gcd(e, \phi(N)) = 1); (e) is the public exponent.
    5. Compute (d = e^{-1} \mod \phi(N)); (d) is the private exponent.

    The public key consists of (N) and (e), and the private key is (d).

    Steps to Digitally Sign a Document

    Programmatically

    1. Read the Document: First, you need to read the content of the document into a suitable data structure (like a byte array).
    2. Compute the Hash: Use a cryptographic hash function (e.g., SHA-256) to hash the content of the document. import hashlib document = "This is a document." document_hash = hashlib.sha256(document.encode()).hexdigest()
    3. Encrypt the Hash: Use the signer’s private key to encrypt the hash. This encrypted hash is the digital signature. from Crypto.PublicKey import RSA from Crypto.Signature import pkcs1_15 from Crypto.Hash import SHA256 # Load private key private_key = RSA.import_key(open("private.pem").read()) h = SHA256.new(document.encode()) signature = pkcs1_15.new(private_key).sign(h)
    4. Attach Signature to Document: The signature is then attached to the document. Sometimes, it is sent separately but referenced to the original document.

    Mathematically

    1. Compute Hash (H):
      [
      H = \text{Hash}(\text{Document})
      ]
    2. Encrypt Hash to Generate Signature (S):
      [
      S = H^d \mod N
      ] Here (d) is the private key, and (N) is part of the public key.

    Verifying a Digital Signature

    1. Detach the Signature: The recipient detaches the digital signature from the document to verify it.
    2. Compute the Hash: The recipient calculates the hash of the received document.
    3. Decrypt the Signature: The recipient uses the sender’s public key to decrypt the received signature to get the hash. Mathematically:
      [
      H’ = S^e \mod N
      ] Here, (e) and (N) make up the public key.
    4. Compare the Hash: Finally, (H’) is compared to the computed hash of the received document. If both are identical, the signature is verified.

    By following these steps, you can digitally sign a document to ensure its integrity, authenticity, and non-repudiation.

    the usage of public and private keys depends on the context—either for encryption/decryption or for signing/verifying signatures. Let’s break this down:

    Digital Signatures

    In the case of digital signatures:

    • Signing: The private key is used to encrypt the hash of a message/document, thereby generating a digital signature. This is done by the sender.Mathematically: �=��mod  �S=HdmodN
    • Verifying: The public key is used to decrypt the received digital signature to get the original hash. This is compared to a newly computed hash of the received message/document to verify its authenticity. This is done by the receiver.Mathematically: �′=��mod  �H′=SemodN

    Data Encryption (e.g., HTTPS)

    In the case of encrypting data for secure transmission, like in HTTPS:

    • Encrypting: The public key is used to encrypt a nonce or a symmetric key. This ensures that only the intended recipient (who holds the corresponding private key) can decrypt it. This is often done by the client.Mathematically: �=��mod  �C=MemodN
    • Decrypting: The private key is used to decrypt the received ciphertext to get back the original nonce or symmetric key. This is often done by the server.Mathematically: �=��mod  �M=CdmodN

    Here �C is the ciphertext, and �M is the original message (nonce or symmetric key).

    Why the Difference?

    • Authentication and Integrity: For digital signatures, the goal is to confirm the origin and integrity of the message. Hence, the private key is used to generate a signature, and the public key is used to verify it.
    • Confidentiality: For secure data transmission, the goal is to keep the data confidential. So, the public key encrypts the data, ensuring that only the owner of the private key can decrypt and access it.

    Combining Both in Protocols Like HTTPS

    Modern secure communication often combines both encryption for confidentiality and digital signatures for authentication and integrity. In the HTTPS protocol, for example:

    1. Asymmetric encryption (using RSA, for example) is initially used to securely exchange a symmetric key between the client and the server.
    2. This symmetric key is then used for encrypting/decrypting the actual data transferred between the client and server, using a symmetric algorithm like AES.
    3. Digital signatures can also be used to verify the integrity and origin of the messages in this secure communication pipeline.

    By combining both methods, HTTPS achieves confidentiality, integrity, and authenticity.