As global warming threatens corals, scientists search for reefs that can take the heat
Researchers say these coral strongholds may help repopulate more degraded reefs.
找到 874 篇相关文章
Researchers say these coral strongholds may help repopulate more degraded reefs.
Why Taking Feedback Positively Can Change Your Career As developers, engineers, designers, and professionals, we all want to improve. We spend countless hours learning new technologies, building projects, and gaining experience. Yet many people overlook one of the most powerful tools for growth: feedback. Unfortunately, feedback often feels personal. When someone points out mistakes in our code, resume, communication, or project, our first reaction is sometimes defensive. We feel offended, frustrated, or misunderstood. I've experienced this myself. But over time, I learned that the ability to accept feedback positively is one of the most valuable skills anyone can develop. Feedback Is Not an Attack One of the biggest misconceptions is believing that criticism is an attack on our abilities. When a senior engineer reviews your code and suggests improvements, they are not saying you're a bad developer. When a recruiter rejects your resume, they are not saying you're incapable. When users report problems in your open-source project, they are not trying to discourage you. Most of the time, people are simply showing you where improvements can be made. The sooner we separate our ego from our work, the faster we grow. Every Rejection Contains Information Many professionals view rejection as failure. I view it differently now. A rejection is data. If ten companies reject the same resume, the market is telling you something. If users consistently struggle with a feature, they're revealing a usability problem. If interviewers repeatedly point out the same weakness, they're highlighting a skill gap. The goal isn't to feel bad about the feedback. The goal is to learn from the information hidden inside it. Growth Begins Where Comfort Ends Positive feedback feels good. Constructive feedback creates growth. Nobody enjoys hearing that their architecture can be improved, their communication needs work, or their project has flaws. But those uncomfortable conversations often lead to th
Crypto Clipper spreads over USB and communicates over Tor.
Recently, while working on an in-progress open-source framework called Projector, I ran into a (not particularly novel) issue: one of it's internal packages ( core ) had grown during this period, and was not nearly as flyweight as it needed to be in the browser. The result was 10-20kbs of unnecessary machinery getting pulled in. I noticed this while running examples. I was consistently hitting a wall in bundle sizes that was surprisingly difficult to get past, even for someone as stubborn and relentless as I am. Naturally, I turned to Claude and ChatGPT to help me with this, and ended up using ChatGPT 5.5 with Codex as I find that, with the "precise" output mode, it tends to be a little more honest than Opus 4.8 these days. I shared exported HAR network logs with it, having it go through the chunks to confirm where the bulk was; consistently, it confirmed that the issue was around an entangling of authoring/resolution code with runtime code in core that was pulling in too much to the browser. The technical details here aren't really important, but I'm using them to illustrate a larger point. We then iterated through a lot of different solutions—I setup a "goal" in codex with benchmarks to hit, and gave it a bunch of constraints, context, and tooling. Finally, after about 2-3 hours of looping against that goal, it completed. Looking through the git diff, I noticed something odd—it had duplicated the result of the resolved module, so it could skip the resolution machinery and thus drop it from the browser bundle (again, technical details not really relevant). It hit the rough kb benchmarks, respected all constraints, utilized all context and skills available, and avoided importing the machinery that we both aligned on being the core problem. It provided an elegant, coherent, well-written api, implemented a surgical, well-tested, well-designed solution, and convincingly defended its work when I queried about the implementation. That sounds great, right? In fact, I thin
Every connected device on your desk, from a smart plug to a fitness band to a hobbyist ESP32 board, runs on a descendant of one tiny chip that was never meant to change the world. In 1971, Intel released the 4004, the first commercially available microprocessor. It was not built for computers, robots, or the internet. It was built to run a desk calculator. The story of how a calculator chip became the foundation of modern IoT is one of the most instructive in all of electronics. A calculator contract that got out of hand The 4004 began as a job for hire. A Japanese calculator company called Busicom approached Intel in 1969 wanting a set of custom chips for a new line of printing calculators. The original plan called for around a dozen separate, purpose-built integrated circuits, each wired to do one fixed task. It was the standard approach of the era: if you wanted a device to do something, you designed silicon that did exactly that and nothing else. Intel engineer Ted Hoff looked at the sprawling design and proposed something radical. Instead of a pile of single-purpose chips, why not build one general-purpose processor that could be told what to do through software? A program stored in memory could make the same chip behave like a calculator today and something else entirely tomorrow. Stanley Mazor helped shape the architecture, and a newly arrived engineer named Federico Faggin turned the concept into a working device, inventing the silicon-gate design techniques that made it physically possible. Masatoshi Shima, Busicom's representative, worked alongside them on the logic. 2,300 transistors that started everything When the 4004 was announced on November 15, 1971, it packed about 2,300 transistors onto a single sliver of silicon. By modern standards that is almost nothing; a current smartphone chip holds tens of billions. But the leap was not about raw count. It was about the idea. For the first time, a complete central processing unit existed on one chip that an
I’ve been a software engineer for more than 12 years. And like many developers, I’ve been watching AI improve at an incredible speed. Every new model seems smarter than the one before it. Tasks that used to take hours can now be done in minutes. Problems that required deep research can often be solved with a simple prompt. A few years ago, we used to say: Think of AI as a junior developer. That made sense at the time. But today, I don’t think that’s true anymore. AI still makes mistakes. Sometimes very obvious ones. But it also comes up with solutions that surprise me. Sometimes it finds an approach I wouldn’t have thought of immediately. Sometimes it helps me solve a problem much faster than I could on my own. And honestly, that’s both exciting and a little scary. But the biggest thing AI changed wasn’t how I write software. It changed how I think about my work. For most of my career, I thought I loved writing code. I spent years doing it. At work, on side projects, and whenever I had free time. Then AI became part of my daily workflow. In the last month, I’ve built more projects than I normally would in an entire year. Ideas that had been sitting in my notes for years suddenly became possible. And that’s when I realized something important: I don’t actually love writing code. I love building things. I love taking an idea and turning it into something real. I love creating products, solving problems, and seeing something that only existed in my head become something people can use. Code was simply the tool I used to do that. And now AI is another tool. That’s why I don’t hate it. In many ways, AI has helped me build more than ever before. It helped me revisit old ideas that I never had time to work on. It helped me experiment faster. It even encouraged me to explore areas outside software development, like animation and content creation. And this isn’t just happening to programmers. AI is changing design. It’s changing writing. It’s changing marketing. It’s changin
When you talk to ChatGPT about a subject you understand well, you quickly notice something. The first answer is rarely the final answer. You add context. You correct an assumption. You explain what has already been tried. You point out that one proposed solution conflicts with another part of the system. After a few iterations, the answer becomes useful. The same thing happens when AI writes code for real products. The difference is that a slightly incorrect explanation in a chat is usually harmless. Slightly incorrect code can become part of your product, pass a superficial review, and remain there for years. This is why successful AI adoption in software engineering is not primarily about generating more code. It is about context engineering : giving AI enough context, constraints, and feedback to generate code that belongs in your system. The First Answer Is Usually Not Enough AI coding tools are very good at producing plausible solutions. That word matters: plausible. The code may compile. The tests may pass. The implementation may even look clean when reviewed in isolation. But software does not exist in isolation. A change must fit the broader system architecture : the current architecture existing domain rules security requirements operational constraints established conventions previous technical decisions future product direction An AI assistant does not automatically understand those things. It knows the code it can see and the engineering context you provide. Everything outside that window must be inferred. And inference is where divergence begins. If you trust the first response without validating its assumptions, you are usually not accelerating engineering. You are accelerating uncertainty. Lack of Context Creates Duplication One of the first visible effects is duplication. AI does not necessarily know that your application already has: a validation helper for the same domain rule an established authorization pattern a shared API client a retry mechani
For the past four years, a sprawling Android-based botnet called Popa has forced millions of consumer TV boxes to relay Internet traffic linked to advertising fraud, account takeovers, and mass data-scraping efforts. This week, researchers from multiple security firms concluded that the Popa botnet is linked to NetNut, a "residential proxy" provider operated by the publicly-traded Israeli firm Alarum Technologies Ltd [NASDAQ: ALAR].
“People just really want to take back control of their time, their lives, their attention... They’re down for whatever helps them do that.”
One of the oldest axioms of software engineering is that code is written primarily for humans to read, and only secondarily for machines to execute. Clean code, expressive variable names, and architectural elegance all serve a single purpose: to ensure that the next developer - or our six-months-older self - can understand what on earth happened. Code has always been a cultural artifact, a shared language, a bridge between human intent and silicon. But what happens to this bridge in the era of vibe coding? We are rapidly moving toward a reality where code is generated by LLMs and pull requests are reviewed by LLMs. When the resulting string of characters is spawned by a machine and audited by a machine, human readability immediately ceases to be a primary metric of quality. This forces a radical question upon us: If code no longer needs to be human, does the code itself need to change? Why do we still cling to Python, to micro-frontends, or to neatly structured repositories? We invented these structures to accommodate the cognitive limitations of the human brain - to keep ourselves from drowning in complexity. An AI doesn’t need these training wheels. To an LLM, a 50,000-line monolithic spaghetti-code mess, completely impenetrable to a human eye, is just as easy to parse as the most pristine clean architecture. So, what is the point of the code itself? Is it possible that code is merely a transitional, obsolete interface—a form of "digital carbon monoxide" that we will soon phase out entirely, replacing it with pure intent and mathematical weights? I ran this exact thought experiment in practice recently when I built a tiny, native macOS utility called Portia over a single weekend (you can check it out here: getportia.app ). The app's function is dead simple: when a port gets stuck (EADDRINUSE), it frees it up with a single click. Throughout development, I was almost exclusively in vibe coding mode. I wasn’t thinking about syntax; I was thinking about the problem an
At least one malware developer is adding text about nuclear and biological weapons to their spyware, in an effort to stop automatic AI analysis. Details : The _index.js payload begins with a large JavaScript block comment containing fake system instructions and policy-triggering content. Because it is inside a comment, it does not affect JavaScript execution. The runtime skips it. The real malware begins after the comment with a try{eval(…)} wrapper around a large character-code array and a ROT-style substitution function. This header appears designed for AI-mediated analysis, not for Node, Bun, or Python. It attempts to derail scanners or analyst copilots that feed the beginning of a file to a language model without clearly isolating the content as untrusted data. In weak pipelines, this can cause refusal behavior, prompt confusion, context pollution, or premature classification before the scanner reaches the actual malware...
VS Code 1.123 adds a two-hour delay before auto-updating extensions to newly published versions, creating a revocation window against supply chain attacks. The delay does not apply to trusted publishers like Microsoft, GitHub, and OpenAI. Similar cooldown mechanisms have now spread across pip, RubyGems, npm, pnpm, Yarn, and Bun. By Steef-Jan Wiggers
A while back I was building a side project that needed public data from a few social platforms. Nothing crazy — profiles, posts, some engagement numbers. I figured I'd just grab each platform's official API. Reader, I did not "just grab each platform's official API." Here's what that road actually looked like, and how I ended up consolidating everything down to one key and roughly ten lines of shared code. The five-API nightmare Instagram (Meta Graph API). Great if you own the account. Useless for pulling public data about accounts you don't. Endless app review. TikTok. The research API is academics-only with a long application. For commercial use, basically nothing. X (Twitter). Used to be wonderful. Now $100/month to start, more for anything serious. YouTube. Honestly the best of the bunch — generous and well-documented. Credit where due. LinkedIn. Partner-only. For most people, no useful public access at all. So to cover five platforms I was looking at: five sets of credentials, five auth flows, five rate-limit models, five totally different response shapes, two flat-out rejections, and a monthly bill. For a side project. What I actually wanted getProfile ( " tiktok " , " someuser " ) getProfile ( " instagram " , " someuser " ) getProfile ( " twitter " , " someuser " ) Same call shape, same auth, same error handling. That's it. I don't care that each platform structures things differently internally — I want one boundary that hides that from me. The consolidation I switched to SociaVault , which puts public data from all of these behind one API and one key. My entire client became this: const API_KEY = process . env . SOCIAVAULT_API_KEY ; const BASE = " https://api.sociavault.com " ; async function sv ( path , params = {}) { const url = new URL ( BASE + path ); Object . entries ( params ). forEach (([ k , v ]) => url . searchParams . set ( k , v )); const res = await fetch ( url , { headers : { " X-API-Key " : API_KEY } }); if ( ! res . ok ) throw new Error ( ` $
Abstract In modern software engineering, writing code that simply "works" is only the first step. The real challenge lies in designing systems that are maintainable, scalable, and easy to test. This article explores the Dependency Inversion Principle (DIP), the final pillar of the SOLID design principles. Through a practical, real-world example in Kotlin, we will demonstrate how to transition from a tightly coupled architecture to an abstraction-based design. This shift dramatically improves our codebase, facilitates unit testing, and prepares our applications for future growth. Introduction: The Chaos of Coupling As applications grow, it is common to see how a minor change in a database schema or a third-party API triggers a domino effect, breaking unrelated parts of the system. This fragility is a direct consequence of tight coupling. Software design principles, particularly SOLID, were established to prevent this architectural decay. Today, we focus on the "D" in SOLID: the Dependency Inversion Principle (DIP). This principle establishes two core rules: High-level modules should not depend on low-level modules. Both should depend on abstractions (interfaces). Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions. The Scenario: An E-commerce Payment Processor Imagine you are building the billing system for an online store. To process purchases, the system needs to connect to a payment gateway, such as PayPal. The Bad Way: Tight Coupling (Violating DIP) In this initial design, our high-level business logic (OrderProcessor) directly instantiates and depends on the concrete low-level class (PayPalService). // Low-level component (Concrete detail) class PayPalService { fun executePayment(amount: Double) { println("Processing payment of $$amount via PayPal API.") } } // High-level component (Business logic) class OrderProcessor { // Tight coupling: this class depends directly on a concrete implementation private val
CEO Tim Cook said in a recent interview that the situation is "unsustainable."
The Quest Begins (The “Why”) Picture this: I’m knee‑deep in a legacy codebase that feels like the Death Star’s trash compactor—every time I try to add a feature, the walls close in and I’m squashed by tight coupling. I’d just spent three hours tracking down a bug that only showed up when the payment gateway was mocked in a test. The culprit? A new PaymentGateway() buried deep inside an OrderService class. It was like trying to defeat Darth Vader with a butter knife—no matter how hard I swung, the Dark Force (aka hidden dependencies) kept pulling me back. I realized I was instantiating collaborators inside the very classes that should be oblivious to their implementation details . The result? Tests that needed a real database, a real Stripe account, and a sacrificial goat to run. Any change to a third‑party API meant hunting down every new scattered across the project. Onboarding a new teammate felt like handing them a map written in ancient Sumerian. Honestly, I was ready to quit coding and become a professional napper. Then, during a late‑night coffee‑fueled refactor session, I stumbled upon a tiny line of documentation that whispered: “Depend on abstractions, not concretions.” It sounded like Yoda giving me a pep talk. The Revelation (The Insight) The magic spell I uncovered is Dependency Injection (DI) —specifically, constructor injection . Instead of a class creating its own collaborators, we hand them in from the outside. Think of it as giving a Jedi their lightsaber rather than making them forge one in the middle of a battle. Why does this feel like discovering the Force? Testability explodes – you can swap in fakes, mocks, or stubs without touching production code. Flexibility skyrockets – swapping a payment provider becomes a one‑line config change, not a scavenger hunt. Clarity reigns – the constructor becomes an honest inventory of what a class needs to do its job. The moment I applied it, the codebase felt lighter, like Luke finally trusting the Force ins
Snap's long-awaited smart glasses debut hasn't exactly done wonders for the company's stock.
By Sergio Colque Ponce — Software Engineering, Universidad Privada de Tacna. Full source code: github.com/srg-cp/design-principles-go When people say "this code is well designed" , they rarely mean it has clever tricks. They usually mean it is easy to change . New requirements arrive every week, and good design is what lets you absorb them without rewriting half the project. In this article I take a small, very common requirement — "send a reminder to the user" — and I show how four classic design principles turn a fragile module into one that is open to change and easy to test. Everything is written in Go , and you can run it yourself from the repository linked above. The requirement We are building the backend of a bank appointment system. When an appointment is created, the user should get a reminder. Today it goes by email . Next month, product wants SMS too. After that, WhatsApp . The pattern is obvious: the list of channels will keep growing. A first (bad) attempt The fastest thing to write is one function that does everything: func SendReminder ( channel , recipient , body string ) error { if channel == "email" { // ... open SMTP, format the email, send it } else if channel == "sms" { // ... call the SMS provider } else if channel == "whatsapp" { // ... call the WhatsApp API } return nil } It works on Monday. But look at what it costs us: Every new channel means editing this function and risking the ones that already work. The function knows about SMTP, SMS providers and HTTP clients all at once: it has many reasons to change . To test the email path you need a real (or faked) SMTP server, because the logic is glued to the transport. This is the design we want to avoid. Let's fix it one principle at a time. 1. Single Responsibility Principle (SRP) A piece of code should have one reason to change . Instead of one function that knows every channel, we give each channel its own type that only knows how to deliver through that channel. Here is the email one: // E
By Sergio Colque Ponce — Software Engineering, Universidad Privada de Tacna. Full source code: github.com/srg-cp/spec-driven-development If you have used an AI coding agent — Copilot, Claude Code, Gemini CLI — you have probably lived this moment: you describe a feature, the agent produces code that compiles and looks right, and then it quietly does the wrong thing. The agent is not weak; the input was ambiguous. We have been treating coding agents like search engines when they behave more like very literal pair programmers. Spec-Driven Development (SDD) is the answer to that problem: instead of jumping straight to code, you write down what you want and why , refine it, and only then let the implementation follow. The specification — not the code — becomes the center of the project. What Spec-Driven Development actually is The idea is old (anyone who has written a Product Requirements Document will recognize it), but it has become practical again thanks to tools like GitHub's open-source Spec Kit . Spec Kit organizes the work into a small set of Markdown artifacts, each feeding the next: Constitution — the non-negotiable principles of the project (security rules, coding standards, architectural constraints). Spec — what you are building and why , with no implementation detail. Plan — the technical blueprint derived from the spec (stack, structure, decisions). Tasks — the plan broken into small, ordered, verifiable steps. Implement — the agent (or you) builds the tasks, with the previous artifacts as structured context. The workflow is usually summarized as Spec → Plan → Tasks → Implement , and the same process is meant to work regardless of language, framework, or which of the 30+ supported agents you use. The real shift is not "more documents." It is this: when requirements change, you update the spec, regenerate the plan, and let the implementation follow — instead of patching code and hoping the intent survives. The spec is a living artifact, not a dusty Word file
A common way to design an application is to begin with its nouns: User Product Order Payment Then each noun receives the standard architectural starter pack: UserController UserService UserRepository The controller receives users, the service services them, and the repository stores them somewhere responsible. This is noun-oriented architecture : treating every important thing in the domain as if it were automatically a useful software boundary. It works for simple CRUD systems. Unfortunately, most applications eventually do something. The noun becomes a drawer Consider a typical UserService : register() findByEmail() resetPassword() changeAddress() disableAccount() mergeAccounts() assignRole() calculateDiscount() These operations all involve a user. That is approximately where their similarity ends. They have different rules, dependencies, side effects, security concerns, owners, and reasons to change. They live together because User was the nearest available noun when the folders were created. As more behaviour accumulates, UserService becomes the official location for anything vaguely user-shaped. Other components depend on it. It gradually depends on authentication, email, permissions, billing, auditing, and several services added during incidents nobody wishes to revisit. The noun becomes both a dependency of everything and a consumer of everything. The folder remains impressively tidy. Name the capability, not the material A better starting question is not: What things exist in this system? It is: What must this system be capable of doing? That leads to components such as: UserRegistrar PasswordResetter AccountMerger OrderPlacer PaymentRefunder SubscriptionCanceller These are agentive names . They name the component responsible for performing a capability. Compare: UserService with: PasswordResetter UserService tells us which noun is nearby. PasswordResetter tells us what the component is for. That difference produces better architectural questions: What rules