Monday, December 23, 2024

Why Software Project Estimation Is Difficult

In my experience, there are three factors that make software project effort/cost estimation difficult:

  1. It is much easier to estimate for the happy path which leaves gaps in specifications for handling edge cases and exceptions.
  2. Not cleaning up code during development.
  3. Frequent changes in libraries and frameworks necessitating unplanned rework.

Handling error cases and edge conditions often takes much more effort than the happy path due to the need for additional logic, testing, and debugging. If edge cases are not considered early, addressing them later can introduce costly redesign efforts. Unfortunately, the number and complexity of edge cases grow as the project progresses, especially if new scenarios are discovered during development or testing. Handling one error case might introduce or expose others, creating a chain of additional considerations that were not part of the original estimate.

Neglecting regular code cleanup leads to the accumulation of technical debt, which increases the time and effort required to implement similar features later in the project. New developers joining the team may struggle to understand and contribute to the codebase, further slowing development. Additionally, the effort required to fix bugs, integrate new systems, or perform upgrades can grow exponentially over time.

APIs, libraries and frameworks are typically updated every six months, and programming languages undergo significant changes every few years. While these updates bring improvements, they can impact project timelines.

Monday, December 2, 2024

Interpreted vs compiled programming languages

With an interpreted language like Python or PHP, if you notice a bug in the web app API logic or the UI script, you can fix it and test the change immediately. This is much faster than stopping to recompile and deploy every time, as would be required in a compiled language.

Without compiled binaries, the same source code can be deployed on multiple platforms without adjustments, as long as the runtime environment is consistent.

Many modern web frameworks (e.g., Flask, Laravel Vite) have features like "hot reloading," which automatically detect changes in code and reload the application without restarting. This is much easier to implement in interpreted environments.

While these advantages are significant, they may come at the cost of runtime performance and error detection, as compilation often catches errors early. However, for most web applications, the increased development speed and flexibility usually outweigh these trade-offs.

Music: The Cardigans - My Favorite Game

Friday, November 29, 2024

Base64 encoding

Base64 encoding ensures that the output only contains characters from a specific, limited set of 64 characters, which are: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/

It is safe for most text-based systems because none of the potentially problematic characters (\, \0, \n, \r, \x1a, ', ") appear in the output. Note that \x1a is the hexadecimal representation of the ASCII control character SUB (substitute). It is a non-printable character with the decimal value 26 in the ASCII table. If included in a text string, \x1a is typically invisible and may disrupt processing, especially in legacy systems that interpret it as EOF.

Example:

Base64 encoding increases the size of the input data by approximately 33%. Specifically: For every 3 bytes of input, base64 adds 4 characters. For example if input JSON is {"customer_id":123,"email":"user@example.com","nonce":"d1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6"}, which is 94 characters, the output will be eyJjdXN0b21lcl9pZCI6IDEyMywgImVtYWlsIjogInVzZXJAZXhhbXBsZS5jb20iLCAibm9uY2UiOiAiZjVkMGMzZGY3ZTM3ODQ2NWQ0NjhkMTdjZTRhNGNlMzIifQ==, which is 128 characters. Note the "==" characters at the end. These are used for padding which ensures the length of the encoded string is a multiple of 4. If your database column to hold the token is VARCHAR(255), assuming a max customer_id of "999 999 999", max email size should not exceed 111 characters.

Email max lengths [RFC 5321, Simple Mail Transfer Protocol]:

  • Local part (before the @): Up to 64 characters (octet = byte).
  • Domain part (after the @): Up to 255 characters.

Total length: The maximum length of a valid email address is 320 characters, but this is extremely rare in practice.

Music: Barış Manço - Dönence

Thursday, November 28, 2024

Concept of Operations

Writing a Concept of Operations (CONOPS) is the first and most important step in a project and should ideally be prepared before a contract is signed. The CONOPS provides a shared understanding of the project's goals, scope, and operational context. This clarity is essential for creating a contract that accurately reflects stakeholder expectations. It bridges the gap between stakeholders' needs and the technical execution. It includes example workflows of at least the happy paths to demonstrate how the system will function. These workflows should clearly specify who is doing what; in other words, there should be no sentences written in the passive voice. Example workflow for email verification on new customer registration:
1. The customer clicks the "Register" button on the registration form.
2. The browser sends a registration request to the server.
3. The server generates a customer email verification token.
4. The server saves the token to the email_verification_token field in the database.
5. The server sends an email to the customer containing the verification link with the token.
6. The server responds with the page: “Registration successful. We have sent you an email. Open that email and click on the verification link to complete the registration.”
7. The browser displays the "Registration successful" page to the customer.
8. The customer opens the email and clicks the verification link.
9. The browser sends a verification request to the server.
10. The server retrieves the customer from the database using the token included in the request.
   a. If the token cannot be found, the server responds with the login page.
11. The server sets the customer's email_verification_token field to NULL.
12. The server responds with the page: “Registration complete. Link: Login”
13. The browser displays the page to the customer.
14. The customer clicks the login link.
15. The browser sends a login request to the server.
16. The server retrieves the customer's data for the login email.
17. The server verifies the password. If the password is correct, the server checks the email_verification_token field. 
18. If the token is NULL, the server logs in the user and responds with the customer dashboard page.
    a. The browser displays the dashboard to the customer.
19. If the token is not NULL, the server responds with the page: “To continue with the login, click on the verification link in the email we have sent you. Link: Send verification email again.”

Sunday, November 17, 2024

Data Transfer Objects

Data Transfer Objects (DTOs) decouple the internal structure of your models, entities, or database schema from the data exposed to the external world (e.g., APIs, front-end). They serve as a contract for the data being transferred, which helps ensure that changes in your data layer don't inadvertently affect the consumer.

Another benefit of DTOs is to enforce structured data and eliminate errors from missing [string] keys. By using a constructor, you control how the raw data is mapped. If a key is missing, you can provide a default value or handle the absence gracefully or you can add validation logic centrally in constructor. Using DTOs allows tools like PhpStorm or VSCode to identify missing properties during development. When working with arrays, typos in keys can go unnoticed until runtime, with DTOs you are using object fields which don’t have this weakness. Example without DTO (note the usage of error prone string keys):

private function prepareProductDetails(array $product): array {
        $product['photos'] = $this->processProductPhotos($product['photos']??[]);
        $product['tr_currency_formatted'] = isset($product['deposit']) 
                ?Currency::formatTRY($product['deposit'])
        return $product;
    }

With DTO:

class ProductDetailsDTO {
    public array $photos;
    public ?float $deposit = null;
    public ?string $tr_currency_formatted = null;
    public function __construct(array $data) {
        $this->photos = $data['photos'] ?? [];
        $this->deposit = $data['deposit'] ?? null;
    }
    public function setFormattedCurrency(callable $formatter): void {
        if ($this->deposit !== null) {
            $this->tr_currency_formatted = $formatter($this->deposit);
        }
    }
}

Note the usage of object fields instead of string keys:

private function prepareProductDetails(array $product): ProductDetailsDTO {
    $productDTO = new ProductDetailsDTO($product);
    $productDTO->photos = $this->processProductPhotos($productDTO->photos);
    $productDTO->setFormattedCurrency(fn($amount) =>
        Currency::formatTRY($amount));
    return $productDTO;
}

Saturday, October 19, 2024

Using UI with AI

If your web or mobile app has multiple user interface (UI) commands, (such as log in, register, search, show products, change user settings), users might struggle to know exactly where to click. The UI would be much more user-friendly if an AI could interpret user speech and convert it into commands that can be handled by the backend. Today’s AI is robust enough to map different phrases that mean the same thing to a single command. For example, a user might say "register" or "create a new account," and both can be mapped to the command "sign_up." The AI can understand both English and Turkish, e.g. "bana yeni bir kullanıcı oluştur" correctly maps to "sign_up". Here is a demo in Python:
When you use an API, such as OpenAI, the main disadvantage is that you must pay for every API call. Therefore, using voice commands to control the UI should be limited to paying customers, and there should be rate limits in place to keep costs under control. You might use open-source models like LLaMA to run the AI on your own server, but that would require better computational and memory resources than you currently have.

Sunday, October 13, 2024

Evaluating fairness of an Investment/Shareholders' Agreement

When an investor invests in a startup, they present the company with an Investor/Shareholder Agreement, which outlines the rights and obligations of the shareholders within the company, including voting rights, share transfer procedures, dividend policies, and protections for minority shareholders. These agreements safeguard the investor's financial interests and govern their relationship with the company and other shareholders.
It is common for the initial investor to have more rights. Normally, early investors expect the following protections:
  • Board representation (a seat on the board).
  • Veto rights on major financial decisions (e.g., capital raises, mergers, or asset sales).
  • Approval rights over key hires or changes in the business direction.
  • Liquidation preferences to get paid first in case of a company exit.
But sometimes their demands can be excessive. To evaluate the fairness of such agreements, you can upload the proposed agreement to chatGPT and use the following prompt:
Does the contract reflect a balanced distribution of power? If not, what share percentage would correspond to the class B shareholder's power? Is this normal for an initial investor in the startup, considering that convincing the first investor is often the most difficult?
If the agreement grants a 15% share in the company but provides 50% control, which is more aligned with a controlling or near-majority stake, we can say the agreement is not fair. Common unfair clauses in such agreements are:
  • Even though the class A shareholder (founder) appoints the board member, any change in the board representative requires class B shareholder (investor) approval.
  • Class A shareholder cannot transfer shares without class B consent for 3 years, while class B share holder is free to transfer to affiliates or related parties without restriction.
  • Important strategic decisions need approval from the Steering Committee, where both A and B share holders have one representative each. Any deadlock in this two-person committee could give the B shareholder veto power over important business decisions​.
You can use the following questions to persuade the investor to be more flexible:
  • Do we agree that the founder/CEO of an ambitious startup with rapid growth goals needs to be able to act quickly, requiring minimal approval/bureaucracy?
  • To fund rapid growth, we most probably will need other investors. Can we foresee that this agreement might irritate potential investors, lower the company's value, and lead them to request the same privileges? 
  • If the same privileges are granted to other investors, would reaching an agreement on any matter outside of routine business—especially considering the potential for irrational behavior (such as ego conflicts, etc.)—become practically impossible?
The goal is to help the investor see the mutual benefits of more flexible terms. You want to highlight that while protecting their investment is important, collaboration, agility, and attracting future investment will ultimately lead to better outcomes for both parties.