0%

Hacking

What should you do in college to become a good hacker? There are two main things you can do: become very good at programming, and learn a lot about specific, cool problems. These turn out to be equivalent, because each drives you to do the other.

The way to be good at programming is to work (a) a lot (b) on hard problems. And the way to make yourself work on hard problems is to work on some very engaging project.

Odds are this project won’t be a class assignment. My friend Robert learned a lot by writing network software when he was an undergrad. One of his projects was to connect Harvard to the Arpanet; it had been one of the original nodes, but by 1984 the connection had died. Not only was this work not for a class, but because he spent all his time on it and neglected his studies, he was kicked out of school for a year. It all evened out in the end, and now he’s a professor at MIT. But you’ll probably be happier if you don’t go to that extreme; it caused him a lot of worry at the time.

Another way to be good at programming is to find other people who are good at it, and learn what they know. Programmers tend to sort themselves into tribes according to the type of work they do and the tools they use, and some tribes are smarter than others. Look around you and see what the smart people seem to be working on; there’s usually a reason.

Some of the smartest people around you are professors. So one way to find interesting work is to volunteer as a research assistant. Professors are especially interested in people who can solve tedious system-administration type problems for them, so that is a way to get a foot in the door. What they fear are flakes and resume padders. It’s all too common for an assistant to result in a net increase in work. So you have to make it clear you’ll mean a net decrease.

Don’t be put off if they say no. Rejection is almost always less personal than the rejectee imagines. Just move on to the next. (This applies to dating too.)

Beware, because although most professors are smart, not all of them work on interesting stuff. Professors have to publish novel results to advance their careers, but there is more competition in more interesting areas of research. So what less ambitious professors do is turn out a series of papers whose conclusions are novel because no one else cares about them. You’re better off avoiding these.

I never worked as a research assistant, so I feel a bit dishonest recommending that route. I learned to program by writing stuff of my own, particularly by trying to reverse-engineer Winograd’s SHRDLU. I was as obsessed with that program as a mother with a new baby.

Whatever the disadvantages of working by yourself, the advantage is that the project is all your own. You never have to compromise or ask anyone’s permission, and if you have a new idea you can just sit down and start implementing it.

In your own projects you don’t have to worry about novelty (as professors do) or profitability (as businesses do). All that matters is how hard the project is technically, and that has no correlation to the nature of the application. “Serious” applications like databases are often trivial and dull technically (if you ever suffer from insomnia, try reading the technical literature about databases) while “frivolous” applications like games are often very sophisticated. I’m sure there are game companies out there working on products with more intellectual content than the research at the bottom nine tenths of university CS departments.

If I were in college now I’d probably work on graphics: a network game, for example, or a tool for 3D animation. When I was an undergrad there weren’t enough cycles around to make graphics interesting, but it’s hard to imagine anything more fun to work on now.

Math

When I was in college, a lot of the professors believed (or at least wished) that computer science was a branch of math. This idea was strongest at Harvard, where there wasn’t even a CS major till the 1980s; till then one had to major in applied math. But it was nearly as bad at Cornell. When I told the fearsome Professor Conway that I was interested in AI (a hot topic then), he told me I should major in math. I’m still not sure whether he thought AI required math, or whether he thought AI was nonsense and that majoring in something rigorous would cure me of such stupid ambitions.

In fact, the amount of math you need as a hacker is a lot less than most university departments like to admit. I don’t think you need much more than high school math plus a few concepts from the theory of computation. (You have to know what an n^2 algorithm is if you want to avoid writing them.) Unless you’re planning to write math applications, of course. Robotics, for example, is all math.

But while you don’t literally need math for most kinds of hacking, in the sense of knowing 1001 tricks for differentiating formulas, math is very much worth studying for its own sake. It’s a valuable source of metaphors for almost any kind of work.[3] I wish I’d studied more math in college for that reason.

Like a lot of people, I was mathematically abused as a child. I learned to think of math as a collection of formulas that were neither beautiful nor had any relation to my life (despite attempts to translate them into “word problems”), but had to be memorized in order to do well on tests.

One of the most valuable things you could do in college would be to learn what math is really about. This may not be easy, because a lot of good mathematicians are bad teachers. And while there are many popular books on math, few seem good. The best I can think of are W. W. Sawyer’s. And of course Euclid. [4]

Everything

Thomas Huxley said “Try to learn something about everything and everything about something.” Most universities aim at this ideal.

But what’s everything? To me it means, all that people learn in the course of working honestly on hard problems. All such work tends to be related, in that ideas and techniques from one field can often be transplanted successfully to others. Even others that seem quite distant. For example, I write essays the same way I write software: I sit down and blow out a lame version 1 as fast as I can type, then spend several weeks rewriting it.

Working on hard problems is not, by itself, enough. Medieval alchemists were working on a hard problem, but their approach was so bogus that there was little to learn from studying it, except possibly about people’s ability to delude themselves. Unfortunately the sort of AI I was trying to learn in college had the same flaw: a very hard problem, blithely approached with hopelessly inadequate techniques. Bold? Closer to fraudulent.

The social sciences are also fairly bogus, because they’re so much influenced by intellectual fashions. If a physicist met a colleague from 100 years ago, he could teach him some new things; if a psychologist met a colleague from 100 years ago, they’d just get into an ideological argument. Yes, of course, you’ll learn something by taking a psychology class. The point is, you’ll learn more by taking a class in another department.

The worthwhile departments, in my opinion, are math, the hard sciences, engineering, history (especially economic and social history, and the history of science), architecture, and the classics. A survey course in art history may be worthwhile. Modern literature is important, but the way to learn about it is just to read. I don’t know enough about music to say.

You can skip the social sciences, philosophy, and the various departments created recently in response to political pressures. Many of these fields talk about important problems, certainly. But the way they talk about them is useless. For example, philosophy talks, among other things, about our obligations to one another; but you can learn more about this from a wise grandmother or E. B. White than from an academic philosopher.

I speak here from experience. I should probably have been offended when people laughed at Clinton for saying “It depends on what the meaning of the word ‘is’ is.” I took about five classes in college on what the meaning of “is” is.

Another way to figure out which fields are worth studying is to create the dropout graph. For example, I know many people who switched from math to computer science because they found math too hard, and no one who did the opposite. People don’t do hard things gratuitously; no one will work on a harder problem unless it is proportionately (or at least log(n)) more rewarding. So probably math is more worth studying than computer science. By similar comparisons you can make a graph of all the departments in a university. At the bottom you’ll find the subjects with least intellectual content.

If you use this method, you’ll get roughly the same answer I just gave.

Language courses are an anomaly. I think they’re better considered as extracurricular activities, like pottery classes. They’d be far more useful when combined with some time living in a country where the language is spoken. On a whim I studied Arabic as a freshman. It was a lot of work, and the only lasting benefits were a weird ability to identify semitic roots and some insights into how people recognize words.

Studio art and creative writing courses are wildcards. Usually you don’t get taught much: you just work (or don’t work) on whatever you want, and then sit around offering “crits” of one another’s creations under the vague supervision of the teacher. But writing and art are both very hard problems that (some) people work honestly at, so they’re worth doing, especially if you can find a good teacher.

Jobs

Of course college students have to think about more than just learning. There are also two practical problems to consider: jobs, and graduate school.

In theory a liberal education is not supposed to supply job training. But everyone knows this is a bit of a fib. Hackers at every college learn practical skills, and not by accident.

What you should learn to get a job depends on the kind you want. If you want to work in a big company, learn how to hack Bulb on Windows. If you want to work at a cool little company or research lab, you’ll do better to learn Ruby on Linux. And if you want to start your own company, which I think will be more and more common, master the most powerful tools you can find, because you’re going to be in a race against your competitors, and they’ll be your horse.

There is not a direct correlation between the skills you should learn in college and those you’ll use in a job. You should aim slightly high in college.

In workouts a football player may bench press 300 pounds, even though he may never have to exert anything like that much force in the course of a game. Likewise, if your professors try to make you learn stuff that’s more advanced than you’ll need in a job, it may not just be because they’re academics, detached from the real world. They may be trying to make you lift weights with your brain.

The programs you write in classes differ in three critical ways from the ones you’ll write in the real world: they’re small; you get to start from scratch; and the problem is usually artificial and predetermined. In the real world, programs are bigger, tend to involve existing code, and often require you to figure out what the problem is before you can solve it.

You don’t have to wait to leave (or even enter) college to learn these skills. If you want to learn how to deal with existing code, for example, you can contribute to open-source projects. The sort of employer you want to work for will be as impressed by that as good grades on class assignments.

In existing open-source projects you don’t get much practice at the third skill, deciding what problems to solve. But there’s nothing to stop you starting new projects of your own. And good employers will be even more impressed with that.

What sort of problem should you try to solve? One way to answer that is to ask what you need as a user. For example, I stumbled on a good algorithm for spam filtering because I wanted to stop getting spam. Now what I wish I had was a mail reader that somehow prevented my inbox from filling up. I tend to use my inbox as a todo list. But that’s like using a screwdriver to open bottles; what one really wants is a bottle opener.

Grad School

What about grad school? Should you go? And how do you get into a good one?

In principle, grad school is professional training in research, and you shouldn’t go unless you want to do research as a career. And yet half the people who get PhDs in CS don’t go into research. I didn’t go to grad school to become a professor. I went because I wanted to learn more.

So if you’re mainly interested in hacking and you go to grad school, you’ll find a lot of other people who are similarly out of their element. And if half the people around you are out of their element in the same way you are, are you really out of your element?

There’s a fundamental problem in “computer science,” and it surfaces in situations like this. No one is sure what “research” is supposed to be. A lot of research is hacking that had to be crammed into the form of an academic paper to yield one more quantum of publication.

So it’s kind of misleading to ask whether you’ll be at home in grad school, because very few people are quite at home in computer science. The whole field is uncomfortable in its own skin. So the fact that you’re mainly interested in hacking shouldn’t deter you from going to grad school. Just be warned you’ll have to do a lot of stuff you don’t like.

Number one will be your dissertation. Almost everyone hates their dissertation by the time they’re done with it. The process inherently tends to produce an unpleasant result, like a cake made out of whole wheat flour and baked for twelve hours. Few dissertations are read with pleasure, especially by their authors.

But thousands before you have suffered through writing a dissertation. And aside from that, grad school is close to paradise. Many people remember it as the happiest time of their lives. And nearly all the rest, including me, remember it as a period that would have been, if they hadn’t had to write a dissertation.

The danger with grad school is that you don’t see the scary part upfront. PhD programs start out as college part 2, with several years of classes. So by the time you face the horror of writing a dissertation, you’re already several years in. If you quit now, you’ll be a grad-school dropout, and you probably won’t like that idea. When Robert got kicked out of grad school for writing the Internet worm of 1988, I envied him enormously for finding a way out without the stigma of failure.

On the whole, grad school is probably better than most alternatives. You meet a lot of smart people, and your glum procrastination will at least be a powerful common bond. And of course you have a PhD at the end. I forgot about that. I suppose that’s worth something.

The greatest advantage of a PhD (besides being the union card of academia, of course) may be that it gives you some baseline confidence. For example, the Honeywell thermostats in my house have the most atrocious UI. My mother, who has the same model, diligently spent a day reading the user’s manual to learn how to operate hers. She assumed the problem was with her. But I can think to myself “If someone with a PhD in computer science can’t understand this thermostat, it must be badly designed.”

If you still want to go to grad school after this equivocal recommendation, I can give you solid advice about how to get in. A lot of my friends are CS professors now, so I have the inside story about admissions. It’s quite different from college. At most colleges, admissions officers decide who gets in. For PhD programs, the professors do. And they try to do it well, because the people they admit are going to be working for them.

Apparently only recommendations really matter at the best schools. Standardized tests count for nothing, and grades for little. The essay is mostly an opportunity to disqualify yourself by saying something stupid. The only thing professors trust is recommendations, preferably from people they know. [6]

So if you want to get into a PhD program, the key is to impress your professors. And from my friends who are professors I know what impresses them: not merely trying to impress them. They’re not impressed by students who get good grades or want to be their research assistants so they can get into grad school. They’re impressed by students who get good grades and want to be their research assistants because they’re genuinely interested in the topic.

So the best thing you can do in college, whether you want to get into grad school or just be good at hacking, is figure out what you truly like. It’s hard to trick professors into letting you into grad school, and impossible to trick problems into letting you solve them. College is where faking stops working. From this point, unless you want to go work for a big company, which is like reverting to high school, the only way forward is through doing what you love.

1. Know your motivations

Why do you want to “get good at Leetcode”? Do you want to get your dream job? Win programming contests? Learn more about algorithms? Without a good motivation, you may struggle. If your only motivation is to get your dream job, then you might also struggle to stay motivated, especially with the “risk” of not getting the job. If you don’t enjoy solving the questions, it might not be worth doing the advanced ones. There are plenty of good companies out there where you won’t need to be able to solve medium-level and hard-level Leetcode questions to get into them.

So, think about what motivates you, and keep that motivation in mind!

2. Set realistic goals for yourself

Multiple times, I’ve seen people ask how many questions they should solve in a day. This is the wrong question to ask though. You shouldn’t have a number-of-questions-per-day target. Instead, you should decide how much time you are willing and able to put towards your learning, and then make it your goal to use that time as productively as you can, aiming to improve. You can only do your best, and you should be aiming to do your best, so comparing to other people won’t be helpful here.

Aim to divide your time between learning new skills, mastering old skills and general problem solving (tackling questions without initially knowing what skills you’ll be applying).

3. If you don’t have a CS degree, do an online data structures and algorithms course

If it wasn’t for the first year data structures and algorithms course I did back when I was in university, I don’t think I would have known where to start. Getting started can be daunting, and knowing what exactly to focus on can be even more so. As a rough guide, you’ll need a course that takes you over key ideas such as Arrays, Sorting, Searching, Linked Lists, Trees, Graphs, Stacks, Heaps, Queues, Hashing (maps, sets, and dictionaries), Big-O notation, and complexity analysis (cost). By the end of it, you should know the use cases for each data structure, and be able to justify the reasons in terms of cost.

A good online course will have assignments and quizzes that’ll help you know whether or not you’re on track and picking up the most important ideas. This will then help you as you move towards self-directed learning on more advanced concepts.

There are also many good resources on the more basic programming skills. Find one that suits your style and language of choice. Make sure you can design programs with loops and conditionals (if statements) with ease, as this is central to algorithm design.

4. Find some good resources for learning more advanced algorithms

It really does help to have a good resource to look at when needed. I use the CLRS algorithms book. I’d highly recommend it, either go for the e-book version on Amazon or look for it in the library (if you’re a university student with access to a library) if the cost is a concern. The Leetcode Explore modules are fantastic too. Geeks For Geeks also has some useful content, although the writing quality varies a lot and the lack of proofreading on some of the articles can make it painful to read. If you can ignore that, great, if not, then you might need to find another resource. Wikipedia also has some good articles, although quality and clarity varies a lot.

5. Learn techniques, not solutions

I’ve seen a few people on the Leetcode forums and beyond asking how many Leetcode questions they’d need to have done to have a good chance of seeing all the questions they’ll get in their interview, and it amazes me that anybody thinks this is a good idea. Leetcode itself doesn’t help the cause by providing lists of questions sorted by company (for subscribers).

If in an interview you’ve already seen the question, it’d be dishonest to pretend you hadn’t. You’d also have to be pretty good at faking the “solving a novel problem” thought process your interviewer is looking for.

Instead, you should be aiming to do enough questions to learn about the common data structures, algorithms, and techniques that you’re likely to need to use. Your goal is to get yourself to a point where if you’re given a problem you’ve never seen before, you can work through it and decide which data structures, algorithms, and techniques you could apply to solve it. It will take a lot of practice to get to this point, and sometimes there’ll be a question where it takes you a few minutes to even know where to start. But the more you learn and practice, the better you will become.

6. Learn the fundamental data structures (Basic)

These next 3 points are concerned with specific concepts you’ll need to learn. I’ve split them into 3 general categories, based on difficulty and how often they tend to come up. I’d recommend mastering all in one category before moving up to the next. The lists aren’t exhaustive, and the order isn’t absolute, but this is roughly the order I worked on learning and mastering concepts.

If you have a lot of trouble with even the simplest questions in the fundamental data structures section (some are hard, I’m not saying you have to find them all easy!), then you might need to find an introduction to data structures and algorithms course to work through. Most of these topics have a Leetcode Explore module, I highly recommend using them. After that, find more problems by searching the problem list by the relevant tag. Focus mostly on easy-level questions for now.

  • Arrays and Strings: Leetcode’s module for Arrays and Strings is where you should start. This is a fundamental topic, so practice lots until you’re really confident.
  • Linked Lists: Again, check out the module for Linked Lists. Don’t worry about the solution guides that are subscribers only—you don’t need them. Check out the discuss forum instead, click on posts until you find a good one. Linked lists can be confusing at first, but they’re easy as once you get your head around the idea of the next pointers.
  • Trees: Focus on the N-ary trees module and then the Binary Trees module. Don’t worry about Binary Search Trees yet, you can look at those a bit later. For some reason, the information documents about traversals are subscribers only. Don’t worry about this though, there are plenty of great free resources around, such as this overview and these widgets that get you to click on nodes in the correct order. You’ll find lots more by googling.
  • Hashing: This is dictionaries/maps, and sets, and is covered under the Hash Tables module. This is one of the most useful topics you’ll learn, so be sure to get lots of practice in, as most challenging questions use these data structures as part of the solution. Don’t worry about the few bits that are subscribers only.
  • Stacks, queues, and heaps (priority queues): These are also fairly versatile, coming up in a lot of problems. Stacks and Queues have a module, although heaps/ priority queues don’t yet. Have a look on Geeks For Geeks for some good articles on heaps, and then find some questions on Leetcode that are solved with a heap. Check if your programming language of choice has a built-in heap type, it will be called either a heap or a priority queue.
  • Two pointer technique: If you didn’t do this with arrays, you should do it now. It comes up a lot.

Make sure you know complexity costs of these basic data structures, for example, that finding whether or not something is in a linked list is O(n), but finding whether or not its in a dictionary is O(1). You should understand the costs inside and out, knowing exactly why they are what they are. Simply memorizing the costs won’t help you much.

7. Learn the more advanced data structures and some famous algorithms (Intermediate)

There are some algorithms and a few more data structures that will come up over and over again. It is well worth putting time into studying each of these, much like with the fundamental data structures. Some of them will be in the easy category and some in medium. The hard ones tend to combine multiple concepts.

  • Recursion: This alternative to iteration is helpful for solving some problems, and once you’re used to it, is often quicker to write than iteration. Leetcode has a module for recursion. Just do this module, don’t worry too much about recursion 2 yet, there are more important concepts for you to learn first.
  • Graphs and graph algorithms: Graph algorithms come up everywhere. I’ve heard that in the interview processes of top companies, getting at least one graph question is inevitable. The main things they are looking for is that you know the 3 main ways of representing a graph: linked, adjacency list, and adjacency matrix, choosing the best one for a given situation, and then running algorithms over it such as breadth-first search, depth-first search, and topological sort. Often graph questions aren’t explicitly listed as such—you’ll be given some data as an input that you need to convert into a valid graph format, and then run the appropriate algorithm over it. You must get really good at recognizing graph questions instantly. Be aware too that a 2D array can represent a special kind of graph where each cell represents a node linked to its neighboring cells. Often these are breadth-first search questions. You should have already seen some of this in stacks and queues, and trees. But be sure you’re super confident with graphs. You should be able to BFS and DFS both iteratively and recursively, and with your eyes closed (okay, maybe not that extreme, but you should be really confident with them!).
  • Union find: This algorithm can initially seem scary, but it’s not. It’s useful for finding the connected components of graphs and solving a number of other problems. Have a look on Geeks for Geeks to get started or the CLRS textbook if you own a copy. There is no Leetcode module on it yet, but there are lots of relevant questions. Learn the simple optimizations, in particular path compression, as while they sound advanced, they’re actually really simple and lead to huge efficiency gains.
  • Binary search: People tend to get quite bothered by this algorithm and its edge cases and resort to guessing and checking loop and conditional conditions. I’d recommend doing the binary search module, but also trying to build intuition around the algorithm. Be cautious on relying too heavily on the templates given in the module, they are good to get started, but trying to memorize the templates will only lead to edge-case bugs that you will struggle to debug. It helps to know whether your “middle index” formula is getting the lower-middle or upper-middle (when there’s an even sized list) and then whether or not your low and high moving will definitely converge instead of infinite looping. Sometimes making it converge is as simple as using the upper-middle instead of lower-middle, but it depends on how you’re changing low and high. Overall, you must build an intuition rather than trying to memorize the algorithm. I might write a blog on this at some point.
  • Binary search trees: These, along with generic binary trees (i.e. not sorted tree), seem to come up almost every week in the weekly competition. Being able to quickly write a recursive function to traverse the tree and get the information you need is possibly the single biggest thing you can do to improve your competition performance. They tend to be medium-level questions, but if you know what you’re doing, they are easy to do in under 5 minutes! Start with the Binary Search Tree module, and then look for more questions on trees. Make sure to do lots of medium-level questions.
  • Tries and advanced string algorithms: Leetcode has a module on Tries for you to work through. As for other string algorithms, Google them or look in the CLRS algorithms book.
  • Interval trees: These are probably the most advanced data structure you’ll see on a semi-regular basis. I’d recommend trying to code one. Unfortunately, Leetcode doesn’t have an Explore module on them.

8. Learn the versatile algorithmic design techniques (Advanced)

This is the stage I’m currently at. It was working on these techniques that has really driven my improved performance in the competitions. They are not easy, and you’ll need to work and work and work at them. You’ll need to start by practicing questions where you already know which technique you’ll be using, and then on ones where you don’t. The competitions are useful for that, as you don’t get to see the tags until the end.

  • Bit manipulation: This isn’t that difficult once you get your head around it, and it’s also pretty fun. Leetcode has many questions on bit manipulation, although no module for it at the time of writing. To get started, Google a guide for your programming language of choice, then have a go at some of the questions. Note that while it can be used standalone for most of the easy and medium level questions, it tends to be an optimization technique combined with other concepts in hard-level questions.
  • Backtracking: This is a strategy that many advanced recursive algorithms use, especially when coming up with solutions for NP-Complete problems. Before I learned how to do backtracking correctly, my exponential time algorithms would also use exponential space, with lists and sets getting copied everywhere. I see many hard-level questions that are straightforward with backtracking. Leetcode has a section on it, tucked inside the Recursion 2 module.
  • Divide and conquer: This is another strategy that many advanced recursive algorithms use, in particular, those that run in O(n log(n)) time (quasilinear). Mergesort and Quicksort are the most well-known examples of divide and conquer algorithms. Leetcode has a section on them, also tucked inside the Recursion 2 module.
  • Parsing: Parsing questions are very difficult if you don’t know how to approach them, but very fun if you do. One technique is recursive descent, but there are also others such as using an explicit stack. Some good questions are Number of Atoms, Parse Lisp Expression, Parsing a Boolean Expression, and Evaluate Reverse Polish Notation.
  • Dynamic programming: This may be the hardest technique, and unfortunately I’m still looking for a good resource on it. The CLRS algorithms book has some good content on it though, and Leetcode has many awesome dynamic programming questions. Come on Leetcode, add an Explore module for this topic!
  • Memoization: This is an alternative to dynamic programming that instead uses recursion. It tends to be easier to reason about (for most people), so it might save you for the more challenging dynamic programming problems. In saying that, your goal should ultimately be to do as many questions as possible using dynamic programming instead of memoization. Leetcode has on it, tucked inside the Recusion 1 module.

Keep in mind too that there’s often more than one approach for a question, and the tags aren’t always exhaustive. For example, the Smallest Sufficient Team problem is tagged as two of these techniques, but also has really nice solutions with one of the others. I’m being intentionally vague as I don’t want to give spoilers.

9. Be aware of the intractable problems, particularly the NP-Complete ones.

A computer scientist’s idea of a practical joke is to give the unsuspecting victim an NP-Complete problem to solve and watch them go around in circles, possibly discovering what they think is a solution and then discovering that it fails in some cases. Okay, maybe we’re not that mean. But I have definitely heard of people in interviews trying to come up with an efficient solution for a problem which is NP-Complete, and if they DID find a good solution, they wouldn’t be needing the job they’re interviewing for (as they’d be rich for it!). I have even heard of companies trying to solve them, not realizing. Or apps that promise to give you the shortest path between multiple stops.

So, know what they are. Wikipedia has some good lists of them. Know what the best way to solve them is⁠—sometimes it’s a brute force, but with clever optimizations. Other problems have dynamic programming solutions which are what we call pseudo-polynomial. The knapsack problem is a good example of this. Sometimes an assumption you’re allowed to make (specified in the problem limits) will mean there’s a polynomial-time solution (or pseudo-polynomial).

10. Brush up on those math skills

If you feel a bit rusty in math, or you didn’t enjoy it in school, it might be time to get out the grid paper and pencil. I did this last year, and wrote a blog post on it. Overall, it was a lot of fun, and it helped to improve my programming and algorithm skills too. I could ramble about this topic for hours, but we don’t have time for that, so here are some free resources I used and recommend:

  • Khan Academy: I can’t recommend this strongly enough. It covers all of primary (elementary) and high school math in great depth, and it’s actually really fun. It’s not just for school students, it’s equally awesome for adults. Sal has a talent for explaining things and the quizzes and content are broken up into manageable chunks. If you aren’t sure where your weaknesses are, have a go at the initial quizzes. They’ll tell you where you have work to do. Don’t be ashamed if you find there are a few primary school-level concepts that you need to work on. It happened to me, and I was amazed at how much addressing those weaknesses helped me with the more advanced stuff. If you aren’t already confident with it, I especially recommend the geometry stuff. The proofs and problem-solving there is a similar kind of thinking to the thinking you’re trying to develop on Leetcode.
  • Introduction to Mathematical Thinking: If you feel you possibly have some amount of math-PTSD from school, Keith Devlin is the doctor for you. Keith’s mission is to enlighten the general public on what it actually is mathematicians do (i.e. not arithmetic and solving pages and pages of equations for y’s x). Aside from that, the course goes over logic and proofs and aims to get you to see the difference between school math and being a mathematician. It’s helpful, especially if you aren’t keen on tackling the MIT OCW stuff yet.
  • MIT OCW Mathematics for Computer science: This is more advanced, but well worth tackling if it aligns with your goals, i.e. being a top competitive programmer or getting into a top tier tech company. If you did a degree in computer science, you probably already did some discrete math. Unless you did discrete math beyond an introduction at a top school though, then this course will be helpful as there’ll almost certainly be stuff you didn’t know. At the very least, it’s great revision. Note that you’ll need to put hours into doing ALL of the “in class” questions to get the maximum benefit. We all learn best from doing, and the questions are of high quality.

11. Be confident knowing the asymptotic complexity of an algorithm, and don’t write code until you know your algorithm will be good enough.

When given a question in an interview or a contest, don’t dive into writing code. The first thing you’ll need to do is think and brainstorm possible ways of solving it. For each one, you need to know how to determine the asymptotic complexity, paying particular attention to the worst cases. You’ll then need to look at the problem limits and make a judgement call as to whether or not your algorithm is likely to be fast enough. Over time, you’ll build a sense for how long things take. For me, I start to worry when plugging the maximum value into the big-O formula evaluates to over a million or so. It’s a very crude measurement, but it seems to serve me well.

Sometimes you’ll need to come up with a better algorithm from scratch. Sometimes, you can optimize a bad algorithm so that it’s a lot better. But you’ll need to be fairly confident before you start coding, otherwise you’ll risk wasting time writing and debugging code, only to get the dreaded Time Limit Exceeded error that can only be fixed with a full re-design and re-write.

12. Read the problem limits well.

Often, you’ll be told what the maximum input sizes to expect are. These will give you a lot of clues about the kind of algorithm you might be writing. As a very rough rule of thumb:

  • If the limits are tiny, i.e. all under 50 or so, then your algorithm will probably be non-polynomial (and this is a “smell” for intractable problems).
  • If it’s under about 1000, then you might be looking at quadratic or around that.
  • If it’s over that, but under 1,000,000, then it’s probably linear or quasilinear.
  • Over that, and it’s quite possibly logarithmic. If it’s ridiculously high (e.g. billions or trillions) like sometimes seen in the Google Code Jam, that can be a sign of being able to shrink it with modular arithmetic or even a constant time algorithm.

These aren’t certain rules, but they definitely give a good indication. On the other hand, if your quadratic algorithm needs to run on sizes of 2000 or more, it’s probably not going to pass. Usually, but not always, quadratic algorithms can be replaced with quasilinear ones taking a divide and conquer approach, or using binary search somehow.

13. Choose the best variable to scale up on.

Sometimes, what seems to be the main variable (e.g. number of items) is quite high, but another variable is surprisingly lower. Going back to one of my favourite questions – Smallest Sufficient Team – we are told this:

1
2
3
4
5
6
1 <= req_skills.length <= 16
1 <= people.length <= 60
1 <= people[i].length, req_skills[i].length, people[i][j].length <= 16
Elements of req_skills and people[i] are (respectively) distinct.
req_skills[i][j], people[i][j][k] are lowercase English letters.
It is guaranteed a sufficient team exists.

The interesting detail here is that the maximum number of required skills is a lot lower than the maximum number of people. This is a big clue. It suggests that the cost will primarily scale with the number of skills required, not with the number of people. Instead of an approach that considers including or not including each person, we should instead be looking at a solution where the main looping is over the skill list.

This is common in Google Code Jam questions. It’s worth having a really good look and think about the limits, especially if one seems unusually low/ is a surprising constraint. It can often mean there’s a good solution that wouldn’t have worked as well if it was bigger.

14. Share your solutions and teach others

It’s widely known that to master something, you have to teach it. Writing up high-quality solutions and explanations on the discussion forums is a great way of doing this. It will also help you develop other software engineering skills, such as writing and explaining. Make sure you post with the goal of helping others, and not showing off. Nobody likes a code dump with no comments or explanations.

Take pride in the code you share. Very few people like reading code where all the variable names appear to have been taken from the lyrics of the famous alphabet song. When writing your post and the code to put in it, pretend potential employers might be reading it. If you’re wanting to get into a top tech company, then write the kind of code you’d be expected to write there. For example, use descriptive variable names. During my Google internship, I noticed this in the Google codebase and my partner James also tells me that Canva’s codebase is the same.

ABOVE ALL OTHER ADVICE, put 3 backticks before and 3 backticks after your code. This will make it format nicely. It amazes me how many people don’t know to do this/ don’t bother to find out how when their code looks hideous and unreadable from the lack of it.

15. Practice general problem solving

When learning algorithms, data structures, and techniques, I recommend you use the tags to find relevant problems. After all, your goal is to master a specific skill.

But after that, you need to practice “problem solving”. This is where you have a problem to solve, and you do not know which of your skills you’ll be applying to it. To do this, the weekly contests and mock interview feature are amazing.

Remember that sometimes a question will require a technique you’ve never seen before, or will require a major adaptation to one. You’ll need to learn to be creative and think outside the box if this doesn’t come naturally to you, memorizing solutions will not help you. A great example of this is questions such as Minimize Max Distance to Gas Station. There is a quasilinear solution using binary search. It’s not at all what you might initially think.

16. Do the weekly contests

These are great fun, and a great chance to practice your general problem solving, although they can be buggy as the questions are new to the site. Cheating is also an issue, particularly in the form of people hardcoding the answers to the largest test cases. This has reduced a lot though since Leetcode stopped displaying the answer to test cases where the result was Time Limit Exceeded. There are also issues with some programming languages being faster than others. Sometimes, a poor solution will slip through with C++ and Java, but not Python, for example.

There is no rule against using previous code, although at the end of the day, one needs to decide what their goal is. Using previous code is not helping you learn. Most of the winners seem to be using previous code, their goal is generally to accumulate Leetcoins so that they can get their yearly subscription and a T-shirt (or so it seems)

It also takes a lot of practice. Over time, you’ll get better at it. I wasn’t getting in the top 200 at first, it took practice.

17. Use the mock interview feature

I have a Leetcode subscription, so in theory, I could do the company-specific interviews. I actually never do though, I much prefer the random sets. They seem to be taken from the company-specific ones, with the difference being you don’t know which company it’s from. So don’t worry about not having a subscription, you’ll still find this feature useful.

As a general rule, the 3 levels (screening, phone interview, and onsite) are more difficult than the last. So, start with the screening ones and then move up from there. Unfortunately, Leetcode will sometimes give you a mock interview you’ve seen previously, there’s not much you can do about this. Once it seems to be happening a lot though, that probably means you’ve done most of their mock interviews of that type. Because there is a finite number of them, it’s also best to not use them until you’ve done some practice (i.e. mastered at least the basic and intermediate skills I’ve listed above).

Fun question: If there are 25 mock interview problem sets, and each has the same probability of appearing, around how many mock interviews will you need to do to see them all? How many repetitions can you expect to see?

In all seriousness, the mock interview feature is great for keeping you focused (it’s timed!) and for forcing you to solve some problems without seeing their tags. Remember what I said about practicing your general problem solving?

18. Don’t spend all your time doing easy-level questions

I used to fall into this trap, I would do easy level questions, trying to do them as fast as I could. Then I realized that was a big waste of time, as it was the medium and hard level questions that I was struggling on in the weekly competitions, not the easy ones. Getting the easy ones solved quickly doesn’t mean much compared to getting the points of the hard ones.

You need to do questions that are challenging, even if they take you over an hour. This is how you’ll learn and improve. As you get better at them, your speed will go up. Focus first on accuracy and skill, and then secondly on speed. Remember that if you can at least get to the point of answering all 4 competition questions in the 90 minutes available, you’re already doing better than most people.

19. Try to avoid writing bugs in the first place

We all know debugging sucks when you’re timed and seeing those precious minutes tick by. The best solution is to not write bugs in the first place. By programming mindfully, with your full attention, you’ll be able to consciously ensure each line is doing what you want it to.

For particularly tricky lines of code, such as binary search boundaries, make a quick example if you have to, and make sure your code does what it’s supposed to on it. Do this as you’re writing the algorithm, not at the end. If you don’t remember the details of a particular library function perfectly, quickly look it up, don’t guess. In an interview, tell your interviewer you can’t remember. Most people forget these things, and there’s a chance they won’t remember either. They can tell you what assumption to make. Also, in competitions, it can help to have a shell up with your chosen language if it’s interpreted. Sometimes I forget how particular things in Python work (especially when it involves complex splicing) so being able to quickly type in an example really helps!

Something else that helps me is writing code on paper first. I find there are less bugs, as I can’t handwrite as fast as I can type.

20. You must have fun!

You’ll burn out in no time if you don’t enjoy it. Don’t go about your studying in a way that makes it feel like a torturous chore. Do what you need to enjoy it.

And if you still just can’t enjoy it, then perhaps it isn’t for you, which is fine. We all have things we do and don’t enjoy.

一:浅谈java及应用

java是一种面向对象语言,真正的面向对象,任何函数和变量都以类(class)封装起来

在说java能做什么之前,先说java作为一个真正面向对象语言的优点

首先第一个,既然是真正的面向对象,那就要做到彻底的封装

这是java和c++最大的不同,java所有的源码以及编译后的文件都以类的形式存在

java没有所谓的类外部定义,所有的函数(方法)以及变量(属性)都必须在类内部定义

这样就不会出现一个类被切割成这里一块那里一块的情况,c++就可以,不是么?

这样做使得整个程序的结构异常清晰,明了

其次第二个,最让人欢呼雀跃的是完全屏蔽了指针,同时引入了垃圾回收机制

任何一个写过c/c++代码的人,都会对内存管理深恶痛绝

因为这使得我们不能把主要精力放在我们关心的事情上

而需要考虑计算机内部的一些事情,作为一个软件工程师

我想没有一个人愿意把大量的时间花在内存管理上,毕竟我们不是电子工程师

此时java的优势体现出来了,它完全屏蔽了内存管理

也就是说,如果你用java写程序,写出来的任何一个程序内存上的开销,都不受你控制

乍一看,似乎你受到了束缚,但实际上不是这样

因为虽然你的程序无法对内存进行管理,降低了一定的速度

但你的程序会非常非常的安全,因为你无法调用一个空指针

而不像以前写c的时候那样,成天因为空指针而担惊受怕

当然,如果你深入了解这一行,就会发现java其实也无法保证程序不去调用空的指针

但是它会在最大程度上避免空指针的调用

这已经很好了,安全,这是java的最突出的优点

第三个,虚拟机跨平台,这是java最大的特点,跨平台

可能所有人都知道windows,但是不是所有人都知道unix

和java一样,很多人都不知道unix这种操作系统干什么用

我不想多说unix的应用,这不是主要,但是我要说,大部分小型机

工作站,都跑在unix一族的操作系统上,比如linux/solaris

unix比起windows有一个最显著的特点,稳定,这就好比思科和华为

思科的机器慢但稳定,华为的机器快但不稳定,作为服务器这一端来说

Unix 在服务器端还是非常有市场的

而且很重要的windows不安全,在ms的宣传中我想所有人都很少看到安全二字

因为windows操作系统针对的是pc用户,pc死机就死机咯,大不了重启

瘟95最经常冒出来的就是蓝屏,在服务器这一端上因为微软没有自己的芯片

所以要做系统有些力不从心啊。扯远了,那么java可以做到在windows上编译

然后在unix上运行,这是c/c++做不到的

那么说到这里,java能做什么逐渐清晰起来

刚才说到了,java程序有一个的特点是安全

这个安全是针对你的系统来说得,系统在跑了java程序之后会特别地稳定

而且还能跨平台,那么很明显,java主要应用于除了windows操作系统以外所有的平台

比如手机,服务器

想想看,如果你写的程序要跑在手机上,而手机有多少款用的是windows?

就算有,那如果你用c/c++,是不是要针对每一款手机写一套程序呢?

累死,那跨平台的java就不用,做到编译一次,随时运行

同样,在服务器这一端,如果我想给一个网络门户站点,比如sina

写一个应用程序,pc的性能肯定无法满足sina这样大站点并发数量的要求

那么它就需要买服务器,那么服务器微软没有市场,而且windows很不安全

那么十之八九会买一个sun/ibm的机器,或者hp,但不管是谁的机器

它装的操作系统也不会是windows,因为windows太不安全了,而且多核的支持太差了

这个有空再说,那么如果你要写一个程序在这样的机器上跑

难道我们就在这个机器上做开发么?当然不可能,一般程序员开发用的都是pc,windows

那么该怎么办?写一个程序,然后再拿到服务器上去编译,去调试?

肯定不可能,所以我们就希望找到一个语言,编译完生成程序之后

在pc上调试,然后直接移植到服务器上去,那么此时,我们就会毫不犹豫地选择java

因为在跨平台以及安全性来说,java永远是第一选择

ok,下面说java的缺点

一慢,这其实是一种误区,这就好比goto语句一样

java也抛弃了指针,虽然看上去似乎变慢了,但是在这个两三年硬件性能就能翻番的年代

速度已经不是我们关心的问题了,而且对于企业级的应用来说

没有什么比安全稳定更重要的,换句话说,我们可以忍受慢,但是不能忍受死机和蓝屏

而且越大型的应用,这种慢的劣势体现得越模糊

因为当系统项目越做越大,任何一个环节做不好都可能影响全局的情况下

安全尤其重要,而且就像goto语句一样

这种过分追求速度的主张会给系统开发和纠错以及维护带来无可挽回甚至不可避免的损失

把内存交给计算机去管理吧,这种代价值得

我们做的不是pc游戏,没必要把内存的那一点点消耗当亲爹

二难看,又是一个误区,很多人甚至拿出java swing控件画出的界面来说

呵呵,其实java不是不能画得好看,IDEA就是java写的IDE,挺漂亮的

但为什么难看呢,是因为swing控件它本身就是unix时代的产物,swing控件贴近unix界面

老外看unix界面其实挺顺眼的,他们就是吃unix饭长大的

而unix又是吃百家饭的,不像ms那么唯利是图,所以不怎么对中国人友好

加上我国又没有公司在做操作系统,所以看上去是不怎么顺眼

其实玩过unix的人都知道,unix对中文的支持一直不怎么好

三我还没想到,其他人补充

二:从JDK说起

在知道了java有什么优点,能做什么之后

就该说一下java该如何去学了

在说java如何去学之前,有必要把java的几个大方向做一个简单说明

早在五年前,嗯,应该说是六年前,也就是99年的时候

sun公司做出了一个决定,将java应用平台做一个划分

毕竟在不同领域,语言应用特性是有区别的

针对不同领域内的应用,sun公司可以发布相关高端标准来统一规范代码

这三大块就是J2SE,J2EE以及J2ME

这个举措今天看来无疑是非常了不起的

正是由于这次革命性的发展,使java从一种小打小闹游戏性的语言

发展成为今天企业级应用的基础

这里要特别说明一下J2SE J2EE J2ME中2的意思

其实2就是英文单词to的谐音,就是to的意思

而不是second edition,当然java 2本身版本号就是1.2,也有点2nd edition的味道

说点题外的,sun公司发布的java版本很有意思

虽然总是写是1.X但其实外界对这种版的说法也就是X.0

比如java 2,其实就是java 1.2

1.3其实就是3.0,1.4就是4.0,现在所说的5.0 其实就是1.5

只是以前我们更习惯叫1.X而已

可能到了5.0以后,就全改叫X.0而不是1.X了

所以以后听到别人说java 5.0,千万别惊讶,其实就是1.5

在这三个J2*E中J2SE是基础,就是java 2的标准版(java 2 standard edition)

也就是最基础的java语言部分,无论学什么java技术,J2SE都是必须掌握的

要使用J2SE就必须安装JDK(java development kit)

JDK在sun公司的主页上可以免费下载,下载后需要安装,具体安装流程看教材

JDK包含有五个部分:核心API,集成API,用户界面API,发布技术还有java虚拟机(JVM)

先说运行环境,运行环境最主要要说的就是java虚拟机(JVM)

前面我们说过java是跨平台的语言,那么如何做到跨平台呢?毕竟每种操作系统都是不同的

java的设计者们提出了一个虚拟机的概念

在操作系统之上建立一个统一的平台,这个平台必须实现某些功能以支持程序的运行

如下图:


| program |


| JVM |


| UNIX | Windows | Linux | Solaris |..


程序员所写的每一个程序都先运行在虚拟机上

所有操作都必须经过虚拟机才能和操作系统交互

这样做不仅统一了所有操作系统,同时也保证了操作系统的安全

要死机的话,死的是虚拟机(JVM)而操作系统并不会受此影响

而我们所说的java运行环境指的主要是JVM,其他的不说了,省略

下面说说JDK(java development kit)的API,其实用JDK来包括运行环境以及开发工具

个人感觉是不恰当的,因为这三个单词仅能说明开发工具,也就是几个标准的API

而没有让人感觉到有运行环境的意思在里面,这是题外

那么什么是API?

简单地说就是Application Programming Interface,应用程序编程接口

在java里面,就是一些已经写好了的类打成的包

这又要解释什么是类什么是包了,简单说一下,包就是类的集合

一个包包含零个或多个类,嗯,具体的可以去看书

这些类是java的基础类,常用的类,可以简单理解成java的工具集

最后说一下JDK的发布技术,其实按我的理解,讲白了就是编译器

将.java文件转换成.class文件的一种技术

这三部分组成了JDK,有了JDK,就可以开发出J2SE应用软件了

最原始的只要用一个记事本写几行代码就可以了

但一般来说我们会使用效果比较好的开发工具,也就是IDE

在J2SE这一块,特别推荐JCreator这款IDE

sun公司的产品,与JDK结合得几乎是天衣无缝,非常适合初学者使用

教材方面中文的推荐电子工业出版社出版的《java教程》初级与高级篇各一本

还有就是《21天学通java》虽然有人说21天系列是烂书,但个人感觉

对于j2se,这本书翻译得已经很不错了,基本没有什么语法错误,语句也很通顺

最后要说的就是《thinking in java》

这本书自然很经典,说得比较细,只是我觉得不太适合初学者,其实也不难

初学者直接看也不成问题,但个人感觉还是找本教材也就是前面推荐的两款来看比较好

基本概念的理解还是用教材的,毕竟thinking in java有的版本翻译得很烂

而且个人建议还是看原版比较好,当然这几本都看最好了,但如果没时间

至少精读其中一本,然后再看其他两本就可以,其实三本书内容也差不多

但看问题的角度方式以及面向的读者也都不同,嗯,怎么说呢,找适合自己的吧

最后要说的是

由于虚拟机的存在,J2SE的大多数软件的使用会比一般桌面软件慢一些

效果不尽如人意,现在大概只有swing控件还在使用吧,其它没怎么听说

J2EE&J2ME

这是java应用的重中之重,如果想拿高薪,最好把J2EE学好

记得以前在csdn上看过一个调查,月薪上万的程序员主要从事哪方面的工作

十个中有八个是做J2EE的,其他两个一个做J2ME,还有一个做嵌入式

也许有些夸张,但也从某一方面说明J2EE人才的稀缺以及应用的广泛

所以如果想学java,只精通j2se是永远不够的,至少还需要时间去了解其它两个J2*E

三:java企业级应用之硬件篇

总算讲到企业级应用了,内容开始逐渐有趣起来

java企业级应用分为硬件篇和软件篇

重点在软件,硬件是外延,严格地说与java没有必然联系

但是,由于java是网络语言,不了解硬件的一些基础知识

软件知道再多也没什么用,不要上了战场还不知道为什么而打仗

硬件是软件的基础,在这个前提下,有必要专门花一点点篇幅来聊一下硬件

硬件,简单地说就是我们实际生活中看得见摸得着的东西

也就是那些冰冷的机器,比如服务器,个人电脑还有网络交换机,路由器等等

那么先抛开网络设备不谈,先来说说计算机电脑的历史

在很早很早以前,人类创造了第一台电脑,那时候的电脑只是一台用来计算的机器

无比大,无比重,无比傻,除了算其它屁事不会做,没有所谓的人工智能与计算机网络

但是总算是诞生了,虽然以今天的眼光去看那时候的机器巨傻无比

只配叫做计算器而不是电脑,没有逻辑思维能力,只会死算

但千里之行,始于足下,反正是造出来了

然后随着时间的推移,制造业发展发展发展

电脑性能逐渐得到提升,速度快了起来,成本也逐渐低了下来

于是人们造出了第二台,第三台,第四台,第五台……第n台计算机

人们就造出了无数台计算机并使其成为一种产品

逐渐应用于许多复杂计算领域,不仅仅是科研,许多生产领域也开始出现计算机的影子

然后又随着时间的推移,人们发现不可能把所有的计算机都做成一个样子

因为各行各业对计算机性能的要求各不相同

于是开始把计算机划分档次,最简单地是按照计算机的大小划分

就是教科书上写的大型机,中型机,小型机

个人感觉这样分纯粹扯淡,还以为是小孩子玩球,分为大球,中球和小球

但是不管怎样,计算机不再是千篇一律一个样子了

按照性能的不同,在不同领域,出现了满足符合不同要求的计算机

几乎在同时,人们也开始考虑计算机之间通讯问题

人们开始考虑将不同的计算机连接起来,于是网线出现了,网络出现了

又随着网络的发展,出现了一下专门为了寻址而存在的机器

这就是路由器和交换机,然后又出现了一些公益性的组织或团体

他们制定了一系列的标准来规范以及管理我们的网络

于是3w出现了,计算机的网络时代来临了

嗯,说到这里,计算机发展到今天的历史大概说完了

我们来详细说说网络时代的计算机以及各个硬件供应商之间的关系

前面说到了,计算机分为大型机,中型机和小型机……

但是现在市场上没有人这样分,要是出去买机器,对硬件供应商说

我要买一款中型机,或者说,我要买一款小型机,硬件供应商肯定会问问题

他们会问你买机器干什么用的?科学计算啊还是居家用,是作服务器啊还是图形设计

但不管怎样,简单地说大中小型机已经没有什么意义了

我们按照使用范畴来划分

简单划分为

服务器,工作站还有微机

服务器(server)

服务器涵盖了几乎所有的大型机以及大部分中型机甚至一些小型机

用通俗点话说衿骶褪悄掣龉?4小时不间断运行提供服务的机器

比如卖飞机票(中航信),比如酒店预定(携程)

比如提供门户站点相关服务(sina),比如电子商务(ebay,amazon,阿里巴巴)

这些服务对机器都有一些特定的要求,尤其强调安全和稳定

工作站(workstation)

工作站其实是图形工作站的简称,说白了,就是某种功能极其强大的计算机

用于特定领域,比如工程设计,动画制作,科学研究等

个人电脑/微机(pc)

计算机网络的最末端,这个应该不用我说太多了

网络时代的pc已经普及到千家万户

说完了分类,我们就来说说各个硬件供应商

首先是服务器还有工作站

这两类硬件供应商主要是以下三家

Sun,IBM还有HP(惠普)

然后是PC

以前IBM还有PC事业部,现在被联想吞并了(蛇吞象)

现在国际市场上有联想和DELL(戴尔),目前戴尔还是国际老大

还有HP康柏

然后是网络,也就是路由器和交换机

这块市场嘛,Cisco(思科)Brocade(博科)还有McDATA三足鼎立

内核(CPU)

PC内核

主要是AMD和Intel,前者最近与Sun公司合作,Sun也有一部分单双核服务器用的是AMD的

服务器与工作站内核

这一块与硬件厂商绑定

还是Sun,IBM,HP三家自己生产

题外

在一些大型主机应用市场,比如卖飞机票

德国的汉莎,中国的中航信,香港的国泰用的都是尤利(美国的公司,英文名我忘了)

其它用的是IBM的机器,现在能做大型机的感觉似乎只有IBM可以

尤利已经快倒了,技术太落后了,现在他们的系统还是fortran写的,连c都不支持

要特别说明的是,一个超大型主机然后多个小终端/pc的结构现在越来越没市场了

将来的趋势是用一整个包含多个服务器的分布式操作系统来取代这些大型主机

因为大型主机更新换代极其困难,一旦数据量超过了主机的处理能力

那么就要换主机,这个成本是极大的,但是如果用分布式操作系统

那就只需要增加小服务器就行了

硬件就大概说到这里,与大多数人没什么关系

因为大多数人压根不可能进入这些硬件领域,除非做销售

说了这么多,只是为了给软件部分打基础而已

//做嵌入式的除外

四:java企业级应用之软件篇

嗯,说过了硬件就该是软件了

这篇是这个系列的重中之重

首先我们来说说什么是软件,统一一下概念

所谓软件通俗地说就是一套计算机程序

实现了某些功能的计算机程序

在很早很早以前,一台计算机的软件是不分层次结构的

一台计算机只有一个系统,这个系统既是操作系统又是应用软件,与硬件紧密绑定

后来经过许多年的发展发展发展

人们把一些与硬件紧密相连的又经常用到必不可少的功能做到一套程序中去

这一套程序就被人们称做操作系统

另外一些可有可无的,不同工作适应不同环境的功能封装到另外一套程序中去

而这一系列程序被人们称作应用软件

如下图:


|应用软件:falshgat/IE/realplayer/winamp..|


|操作系统:UNIX/Windows/Linux/Solaris… |


前一篇我们知道,硬件分为服务器工作站与pc

其实无论哪种硬件的软件,都有操作系统与应用软件

ok,那下面我们来谈应用软件

在现在企业级应用中,我们的应用软件一般分为三层

三层分别是表示层,业务逻辑层,数据持久层


|表示层|业务逻辑层|数据持久层|


我们来说说三层中的代表软件

表示层

这一层一般在客户端pc机上,最常见的是IE浏览器,这就是表示层的软件

表示层是直接与使用者交互的软件

业务逻辑层

这一层一般在服务器端,顾名思义,所有业务逻辑处理都在这一层完成

最典型的是appserver,比如IBM的websphere,BEA的weblogic还有tomcat/jboss等

这一层也是三层中的重点,我们要说的大部分内容都是关于这一层的,这个等会再说

这一层就叫做中间层

数据持久层

这一层典型的就是数据库,一般也在服务器端

但该服务器一般与装业务逻辑层软件的服务器分开

当然你也可以用IO输入输出流往硬盘上写东西

但没人会建议你这么做,因为这样做你的数据缺乏管理,不管怎样

这一层要做的就是保存数据,业务逻辑层软件一般不负责保留数据

或者说业务逻辑层只负责暂时储存数据,一关机,业务逻辑层数据全部over了

那么数据的持久化(也就是储存数据)就必须要在这一层完成

下面放着这些概念不谈,我们来说说将来的趋势

趋势一:

瘦客户端,很早很早以前,当时C/S模式也就是client/server

客户端软件大行其道的年代,一个pc用户,是采用一个傻终端连接到服务器上

然后进行相应的操作,最典型的就是我们上bbs经常用的c-term

这就是那个时代的产物,同样还有我国现行的机票定座用的e-term

后来呢,浏览器变得非常流行,人们发现,浏览器也能传递一些数据

虽然这些数据并不像那些终端那样准确,但应付大多数日常需求足够了

于是人们就提出一个瘦客户端概念,也就是说,将来表示层所有的其他软件疾挥?

我们唯一需要的就是一个网页浏览器,然后通过浏览器输入ip地址连接到服务器

然后进行相关的操作,由于网页浏览器一般每个操作系统都有自带一个

这样做就达到了给我们客户端瘦身的目的(不需要安装额外软件)

这样模式被称作B/S模式,也就是browser/server模式

但需要指出的是,虽然瘦客户端是趋势,但并不代表胖客户端没有市场

尤其是一些复杂的业务操作,还是浏览器这种简单软件无法胜任的

趋势二:

傻数据库,ok,首先,我承认,这个名词是我发明的,但我实在无法找到一个更好的表达

什么是傻数据库,如果谁对数据库有所了解的话,就知道,以前的数据库

有自己的一套管理体系,甚至有自己的客户端,比如oracle,mysql,sqlserver都有

在某个管理工具上写什么sql语句查询数据库是我们以前常做的事

那么将来我们提倡的是:将所有的业务逻辑封装到业务逻辑层去

管理的事情由软件来做,由业务逻辑层的软件来做

所谓傻数据库就是说,将来的数据库什么事都不用做

只用把数据给我保存好就行了,那些复杂的业务逻辑什么外键什么关联

都没数据库什么事了,都交给业务逻辑层软件来做

这样做的好处就是:我们就不需要这些该死难懂又复杂的数据库系列管理工具了

而且这些工具每个数据库都有自己的工具,完全不一样,乱七八糟,没有人喜欢面对他们

除了数据库维护人员,也就是DBA,我们是软件工程师,维护的事让他们去做

而且严禁数据库维护人员改动数据库的数据,他们只做备份,必要时候恢复一下就是了

了解了这两个趋势之后,是不是有种砍头去尾保中间的感觉?

没错,未来的趋势就是中间件时代,中间件工程师将是未来计算机应用的主流

那再次统一一下概念,什么是中间件?

记得我上学的时候,看ibm的教材,看了半天中间件定义,就看懂记住一句话

中间件是做别人不愿意去做的事情,现在想想,狗屁定义,呵呵

什么是中间件,中间件是业务逻辑层的应用软件

是处理业务数据与客户端之间业务逻辑的一种应用软件

一种提供网络服务的服务器端应用软件

举个非常简单的例子,网上银行,某个人想用IE进入工行的账户,然后转帐

在这个例子中,客户端表示层显然是IE,数据持久层显然是银行的核心数据库

那么中间件是什么?中间件就是提供这种服务的系统

这三层的划分如下


|表示层 | 业务逻辑层 | 数据持久层 |


| IE | 网上银行 | 数据库 |


五:企业级应用之中间件

前面一篇简单介绍了一下应用软件的分层
下面重点介绍一下中间件,也就是业务逻辑层的软件结构

从本系列第二篇我们知道,java程序是跑在虚拟机之上的

大致结构如下:

| grogram |

| 虚拟机 |

| 操作系统 |

也就是说操作系统先运行一个java虚拟机,然后再在虚拟机之上运行java程序
这样做的好处前面也说过了,就是安全,一旦出现病毒或是其他什么东西
挂掉的是虚拟机,操作系统并不会受多大影响

这时候有人可能会问,为什么非要虚拟机?把操作系统当成虚拟机为什么不行?
可以,当然可以,但是这样做某一个应用软件的bug就可能造成整个操作系统的死亡
比如说我们在某个服务器上安装了一个收发电子邮件的软件和java虚拟机
那么一旦黑客通过收发电子邮件的软件入侵系统,那么操作系统就整个玩完
那么如果黑客通过java程序进行攻击的话,那么死的将会是虚拟机而不是操作系统
大不了虚拟机崩溃,而操作系统正常运行不受任何影响

举个简单例子,比如说最常见的是将数据库(DB)与中间件放在同一台服务器上

| program | |
| DB |

| 虚拟机 | |

| 操作系统 |

那么此时如果没有虚拟机,黑客病毒攻击中间件系统,就有可能造成操作系统的死亡
那此时数据库也有可能跟着一起玩完,那损失可就大咯
那如果此时有虚拟机,那么一旦被攻击,死的是虚拟机,操作系统与数据库不受任何影响

嗯,回顾完虚拟机,再来介绍中间件
在很早很早以前,任何一家企业,想要搭建一个局域网系统,他需要请许多个工程师
比如说我们想搭建一个网上银行,客户端用浏览器,后台数据库比如说用oracle

那么搭建这样一个网上银行,可能需要用到多少个工程师,我们来算一算
首先,由于客户端用的是浏览器,我们需要一些了解网络通讯协议以及一些浏览器标准的网络工程师
其次,由于后台数据库用的是oracle,那我们还需要请oracle的工程师,因为数据库这一层每个数据库公司的接口什么都不一样
然后,我们还需要一些操作系统的工程师,因为我们的系统需要跟操作系统直接交互
最后,我们需要一些设计网上银行系统及其相关业务的工程师

太多了太多了,这样一个中间件队伍实在太庞大了,制作维护成本实在太高了
不仅如此,这样一个中间件就算做出来,他们所写的代码也只能满足这一家公司使用
其它公司统统不能再用,代码重用率极低,近乎不可能重用
毕竟这个系统中改动任何一个部分都有可能涉及到整个系统的改动

那么如何降低成本?

我举出了四组的工程师:
网络工程师,数据库工程师,操作系统工程师以及设计网上银行系统的业务工程师
除了最后一组设计网上银行的业务工程师之外,前面三组工程师是不是每一个项目都需要的?
就算不是每一个项目都需要,至少也是绝大多数项目需要的吧?
哪个项目能够脱离网络,数据库和操作系统?不可能,在这个时代已经很少很少了
好,那既然每个项目都需要,我们是不是可以用一个产品来取代这三组的工程师呢?
我们的业务工程师只需要遵循这个产品所提供的接口,进行相应的开发就行了
人们提出了一种叫做appserver也就是应用服务器的东西
应用服务器是干什么的?按官方的说法,应用服务器是包括有多个容器的软件服务器
那容器是什么?容器(Container)到底是个什么东西我想多数人还是不清楚

在说这个之前,先介绍一下组件
什么是组件,组件是什么?组件其实就是一个应用程序块
但是它们不是完整的应用程序,不能单独运行
就有如一辆汽车,车门是一个组件,车灯也是一个组件
但是光有车灯车门没有用,它们不能跑上公路
在java中这些组件就叫做javabean,有点像微软以前的com组件
要特别说明的是,由于任何一个java文件编译以后都是以类的形式存在
所以javabean肯定也是一个类,这是毫无疑问的

好,那么容器里装载的是什么呢?就是这些组件
而容器之外的程序需要和这些组件交互必须通过容器
举个例子,IE发送了一个请求给容器,容器通过调用其中的一个组件进行相关处理之后
将结果反馈给IE,这种与客户端软件交互的组件就叫做servlet

但是组件有很多种,那么如何区分这些组件呢?
有多种管理办法,比如同是同样是servlet,有些是通过jsp生成的
而有些是开发人员自己写的,那么通过jsp生成的servlet集中放在一个地方
而开发人员自己写的则需要在xml里面配置一些基本的参数
同时,不同组件有可能还需要继承一些特定的父类或者接口,这也是容器管理的需要
还有其他的一些组件,这里就不一一说明举例了

那么容器有很多种,按照他们装载的组件类型划分
比如有装ejb的ejb容器,有装servlet与jsp还有静态页面的web容器等等
//这种只含有web容器的应用服务器也被叫做web服务器

当表示层的应用软件通过网络向appserver发送一个请求的时候
appserver自动找到相应容器中的组件,执行组件中的程序块,把得到结果返还给客户
而我们要做的事就是写组件也就是javabean,然后放到appserver里面去就可以了
至于怎样与IE通讯,怎样截获网络上的请求,怎样控制对象的数量等等
这些繁琐而无味的工作我们都不管,都由appserver去做吧,把注意力集中在业务逻辑上

appserver与其他相关软件的关系如下图:

| | —————– | |
| IE | | javabean | | |
| -> —————– -> DB |
| client <- appserver <- |
| |————————-| |

虚拟机
Windows Linux/Saloris LinuxSaloris
————– ————————- ————
图上可以看出:虚拟机负责处理中间件与操作系统之间的交互
appserver则负责组件的管理以及与其他两层的业务交互

1 附图: image002.gif (76463 字节)

要说明的是上图中还包含有应用程序客户端容器(Application client container)
管理应用程序客户端组件的运行,应用程序客户端和它的容器运行在客户机
这种情况比较复杂一般说的是两个server之间的通讯
比如jsp/servlet容器在一个服务器上,而ejb容器在另外一个服务器上等等
这是分布式操作系统大面积应用的基础,这个以后再说
下面这张相对简单:
2 附图: j2ee.gif (8226 字节)

嗯,那么话题再回到中间件上去,什么是中间件?
appserver就是所谓的中间件,但是中间件不仅有appserver,还有其他的东西
换句话说,appserver只是中间件的一种
而关于中间件有诸多规范以及遵循这些规范的模型
最流行的规范无非两种,一个是j2ee还有一个是.net
但是.net几乎只有微软在用,所以很多人把.net这个规范就当成是微软的中间件产品
也不为过,毕竟没几个公司喜欢跟着微软屁股后面跑的

六:java企业级应用之综合篇

我们知道中间件有很多种规范以及相关的模型
最流行的一个是j2ee还有一个是.net
那么各大公司关于这两套规范各有什么产品以及周边呢?

j2ee:

黄金组合
操作系统:Solaris
应用服务器:Weblogic
数据库:Oracle
开发工具:JBuilider/IntelliJ IDEA
优点:性能一级棒,大企业大公司做系统的首选,世界五百强几乎都是这套组合
缺点:极贵

超级组合,也是最安全最酷的黄金组合,硬件采用SUN公司的机器
但是SUN的服务器很贵,同等价格不如去买IBM的机器
SUN的服务器支持Solaris的效果自然不用说,Solaris号称是世界上最安全的操作系统
Oracle也是世界上最安全,性能最优的数据库,Weblogic是当今性能最优的appserver
JBuilder和IDEA各有所长,JBuilder是Borland公司的招牌之一
是当今世界上最流行的java IDE,用delphi写的,但网络上评价似乎不是很好
IDEA拥有插件功能,界面在所有java IDE中最为漂亮,东欧人开发的产品
东欧人严谨的作风在这个产品上体现得尤为突出,用java写的
IDEA甚至号称自己被业界公认为是最好的IDE//个人保留意见,没有最好只有更好
但我用JBuilder的时候发现了不少bug,而至今还没有在IDEA上发现什么bug
个人推荐IDEA
价格方面,Solaris开源,但是SUN的服务器比较贵,Weblogic最高是34万
oracle标准版要18.6万,企业版要49万,JBuilder要2.7万左右
IDEA零售价大概是500美金,也就是5000多元
另外,虽然理论上这些产品的综合性能要高于其他选择,但是必须看到
由于产商之间的利益冲突,比如oracle也有自己的appserver,但是性能不怎样

使得这几种产品之间协作的性能要比预想中的要差一点点

开源系列
操作系统:-
应用服务器:JBoss
数据库:MySql
开发工具:Netbeans
优点:便宜,性能未必最佳,但是对付中小企业足够了
缺点:出了问题自己抗吧

嗯,这是java阵营最大的特色,免费免费,还有在开发工具这一栏Eclipse也是免费的
但后面要说,算了,换个有代表性的开源产品来
tomcat仅有web容器而没有ejb容器,而jboss已经集成了tomcat
也就是说下载了jboss之后,启动的同时也就启动了tomcat
jboss在tomcat基础之上多加了一个ejb容器,使得jboss+tomcat成为和weblogic
websphere之外又一个得到广泛应用的appserver
现在大概是这样,中小型企业多用jboss,如果应用小一点就用tomcat
只有给那些大型企业做的项目,才会花钱去上一个weblogic或者websphere
mysql也是开源的数据库,做得非常不错,如果系统对数据库要求不高
或者安全要求不是非常严格,mysql是一个非常不错的选择
开发工具方面,netbeans是sun公司极力推广的一种IDE
听说在北美市场使用量已经超过eclipse了

操作系统,软件再不用钱,服务器也要钱,看这台机器上跑什么操作系统就用什么了

IBM套餐
操作系统:Linux
应用服务器:Websphere
数据库:DB2
开发工具:Eclipse/WebSphere Studio
优点:服务好,IBM可以提供全套服务,也可以替客户承担风险
缺点:把机器数据全部交给IBM,安全什么的都由不得你了

呵呵,IBM全套产品,甚至包括硬件设备IBM的服务器
由于是一个公司的产品,各产品之间的协作自然不错
价格方面,Linux,DB2,Eclipse都是开源产品,Websphere目前零售价是33.8万人民币

IBM服务器不错,可以考虑

.net:

微软阵营
操作系统:Windows
应用服务器:.net应用服务器(好像叫IIS)
数据库:SqlServer
开发工具:MS Visual Studio
优点:客户端的用户体验良好,和客户端诸多微软产品的兼容性强
缺点:离开了微软,寸步难行,和其他任何一家公司的产品都不兼容

微软的东西,怎么说呢,太专横了
微软所有的东西都是围绕着windows来做的
.net其实已经可以实现跨平台了,但是微软出于自身商业考虑
在其应用服务器跨平台的实现上设置了种种障碍
而且针对windows,微软做了大量的优化,可以这么看
.net就是与windows捆绑的一套产品
所以有些人说,微软的产品离开了windows,就是渣
而且.net开源选择也少,安全性方面考虑,windows本身就有一堆补丁要打了
sqlserver也不安全,至于.net到底安全不安全我不清楚,毕竟我没怎么用过
但整体考虑,感觉.net不是大企业的首选,鉴于其浓厚的商业背景
也不是中小企业的首选,但是必须看到
客户端也就是微机pc市场已经完全被windows所垄断
所以在一些快速开发,还有和微软产品兼容性要求较高的领域,.net还是比较有市场的
最后一个visual studio对它之前的版本兼容,且支持c,c++,c#,vb等语言

在其传统领域,比如写一些桌面软件等客户端应用上,.net还是第一选择

最后要说明的是
这些组合不是固定不变的
由于J2EE得到了绝大多数IT企业的支持以及JAVA跨平台的特性
我们可以自由地定制个性化的组合
比如我可以选择windows+jboss+eclipse+oracle
也可以选择solaris+websphere+IDEA+mysql
等等,这些自由组合都是可以的,但是有一点必须说明
微软的东西,一般来说离开了windows就不能用
比如你选择了.net应用服务器,那操作系统就必须是windows
你选择了sqlserver,那就必须在windows上用
还有就是遵循j2ee规范的所有的组件都可以在不同的应用服务器上互相移植
比如你可以在测试的时候用jboss
而在正式投产上线的时候使用websphere,只需要在配置文件中作相应改动即可

七:java企业级应用之术语篇

在了解完J2ee的相关周边产品之后需要深入J2ee规范内部去了解一下到底这些规范
这里介绍几个最常用的规范
再继续说下去之前有必要说几个常识

Java的诞生
Java之父James Gosling早年从cmu毕业之后
从事了一段时间的开发工作,后来意外碰到一个项目
这个项目要求他用C++开发,但可爱的JG是天才,凡是天才在某方面特别突出的同时
必然有一些天生的缺陷,恩,或说共性,比如说懒,急躁和傲慢
JG既然是天才,那就必然具备这些共性,JG懒,以至于他学不好C++
不仅他学不好,当年开发出Java的那个团队也都学不好C++
他们急噪,以至于他们中有人甚至威胁以辞职的方式离开这个需要使用CPP开发的项目
他们傲慢,所以他们决定开发出一种新的语言来取代那个该死的CPP
更可爱的是,他们一开始居然给这门语言起名C++++–//没错,我没敲错
叫什么C加加 加加减减,意思是加上一些好东西,减去一些坏东西
天才的设定,有时候你会发现天才和傻瓜真的只有一线之隔
还好这个可爱的名字没有被继承下来,这些天才们给他们的产物起名叫Oak//橡树
只是后来当他们去注册这个名字的时候,发现这个名字已经被注册了
于是在Sun公司的一个女职员//mm就是心细,这个说法也是我们公司mm告诉我的
的提议下,把这个可爱的语言起名为Java,就是他们当时喝的咖啡的名字
所以我们看到Java的标志就是一杯冒着热气的咖啡

JavaBean 了解完Java之后,再来说说什么是JavaBean//华为面试题
JavaBean是什么? 咖啡豆
ja,更为科学点的解释是
用java语言编写的可重用的软件组件//组件的定义前面说过了,不再重复
很形象不是么? 将javabean放入杯子//容器,还记得容器的概念么?web容器,ejb容器
就可以冲泡//编译 成咖啡,供客人们品尝//运行
完美的服务

下面进入正题 再谈容器
前面介绍过容器,我觉得有必要再补充一点
容器从某种意义上说其实就是一个可运行的java写的应用程序
犹如c++/c编译后生成的.exe文件
不同的是java编译后的文件需要用命令行或者脚本启动执行
由于容器是由java写的,所以容器都能够跨平台
虽说如此,似乎大部分容器都针对不同的操作系统提供了不同的版本
但可以肯定的一点是,相同容器间的移植组件不需要重新编译

Servlet web容器组件
Servlet确切地说,就是web容器运行的java组件
与普通javabean不同的是,Servlet定义了一系列方法//比如init()和destroy()
供容器调用,调用的主要目的是为了管理
当一个request请求被web容器截获之后,容器分析该请求地址
然后通过一个配置文件中的映射表//web.xml
调用相应的Servlet组件处理后将结果返还给客户端

JSP//Java Server Page
web容器组件
Servlet出现了之后,人们发现几乎没有办法用一个非常直观的方式去编写页面
毕竟页面是html语言编写的
而让我们用一种流程式的处理方式去逐行教计算机如何写html代码太困难
在这种情况下JSP应运而生,JSP将java代码嵌入html代码内部
然后存成.jsp文件,再由计算机编译生成Servlet储存起来//注意这个过程
所以JSP和Servlet对于web容器来说其实是一种东西,虽然它们编写遵循的标准有所不同
极大地简化了代码同时增加了代码的可读性,生产维护成本下降
值得一提的是,在制定JSP规范的过程中,借鉴了ASP的很多规范
写过ASP并熟悉Java语言的人应该能很快掌握JSP

EJB//Enterprise JavaBean
ejb容器组件
随着时间的推移,人们发现普通的JavaBean似乎并不能满足企业级应用的需要
最典型的就是虚拟机提供的垃圾回收收集机制也就是GC不够完善
可以优化的余地极大,在这种情况下,EJB应运而生
EJB和其它组件一样,不过遵循了某些规范而已
但是这些规范更多的是为充分利用机器并提高性能为主要目的的
举个简单例子
比如某个web服务器有100个用户同时连接上
由于网络连接是瞬时连接,所以很多时候并发数并没有100那么大
前一秒有可能有30个请求被发送过来并被处理
后一秒可以只有10个请求被发送过来并被处理
只有在非常非常极端的情况下才有可能发生100个请求同时被发送过来并被处理的情况
那么我们是否需要保留100个那么多个对象在服务器的内存里面去处理这些请求呢?
很显然,不需要,大多数时候//甚至可以说是所有时候,我不相信有那么极端的情况
我们只需要保存其中的10-30%就够了,那么什么时候需要20%,什么时候需要50%
甚至100%,这个过程就交给容器去管理,这就是ejb容器每天在干的事
管理内存中活跃的对象

恩,必须强调的一点是,由于使用的不成熟
我们经常把规范以及具体的应用两个名词混用
举个简单例子,我们说Servlet,极有可能说的是Servlet规范
也有可能说的是一个具体的Servlet,这个就要看情况而定了
EJB,JSP也是如此

JDBC
和数据库的连接
这个严格说来是数据库产商需要关心的事
关于AppServer如何与数据库的连接
但是也需要开发人员做一点事,因为AppServer不知道什么时候组件需要用到数据库
同时也需要开发人员告诉AppServer他们使用的是什么数据库,ip地址等等
JDBC就是关于这一套东东的规范
包括数据库的产商应提供什么样的接口
AppServer应用服务器应该如何去连接
开发人员应该如何去配置这些连接等等
还有一些数据源,连接池等概念参考相关数据在此就不再赘述
其它的规范比如JMX等确切地说与开发人员关联并不大了
这类高级应用只对AppServer应用服务器产商重要
也不再罗嗦了

记得听说过这样一种说法
大一时候不知道自己不知道 大二时候知道自己不知道 大三时候不知道自己知道 大四时候知道自己知道 为什么呢,因为大一时候刚进大学,什么都不懂,很正常,大家都一样
大二或者大三时候开始接触知识,虽然还是不懂,但慢慢地开始学习,开始积累
过了一段时间,知道自己知道了//也就是前一种说法的大四,后一种说法的大三
开始屁癫,开始拽得不得了,觉得自己怀才不遇,千里马难寻伯乐的那种感觉
有些人是大四毕业了以后开始拽,悟性高一点的,大三就开始拽,因人而异
这几乎是每一个初学者经过一段时间学习后的必然阶段
不管如何,总之开始入门了,这也不是坏事
但最后每个人都会知道自己不知道的,也就是后一种说法的大四阶段
//前一种说法里面的那些家伙估计要到工作以后才能明白
因为任何一门学科都博大精深,要是能在两三年之内就统统搞懂
那不是在吹牛就是坐井观天,java如此,c如此,c++也是如此

那么到了本系列的第七集,可爱的读者应该处在什么阶段呢?
恭喜,在看完这篇文章之后,你就基本处于知道自己不知道的那种阶段
离拽起来还有那么一段距离,因为你们毕竟还没有学习和积累一定的基础知识
但是骗骗外行,蒙蒙国企那些吃闲饭的管理人员问题不大

八:java高级应用之框架篇

没错,我没敲错
之所以不再声称是企业级应用而称之为高级应用 是因为下面要讲的东西属于纯民间性质
是java具体应用的上层建筑,可用可不用,没有人强迫你用

首先给框架//framework 下一个定义
我想读者你可能听说过.net framework这个概念
没错,我们将要说的framework也和这个framework差不多
所不同的是.net framework的竞争对象是j2ee那一系列标准
而我们将要说到的几个框架则应用在j2ee的不同层面
单就单个框架而言,没有.net framework管得那么多
但是却要比它精专多了,而且总量加起来,也远比微软那一套框架要广泛得多
回到正题,框架是什么?
软件工程之所以被叫做软件工程就是因为有那么一批人觉得可以用工程学里面
那些管理Project的方法来管理软件从开发到维护这一系列流程
那么在建筑工程里面框架是什么?
现在建筑多采用钢筋混凝土结构,注意里面一个很重要的词汇:钢筋
托福阅读中曾有一题听力就是关于钢筋结构的诞生,在美国
恩,现代建筑中多在建筑起来之前,先用钢筋搭建出一个框架出来
然后往钢筋中间填入混凝土,从而形成一个完成的建筑
而今天要说到的框架就是这么一个东西在每一个软件中间的实现
框架就是那么一个通过预先写好代码从而帮我们建立起一个软件结构的这么一个东西

这里提一下框架与规范//主要指J2ee规范也就是官方标准的区别
从某种意义上说,J2ee规范本身就是一个框架
无论是web容器也好,还是ejb容器也好,它们都开发了一部分通用的代码
并且帮助我们搭建起来了一个软件结构,我们要做的就是往里面填入组件
比如ejb/servlet/jsp等等
没错,要这么理解也没错,但是为了避免混乱,我们还是严格区分开来
本文中将要提到的框架如无特别说明,就是指的是非官方标准的框架
规范是规范,而框架是建立在规范之上的一种东西
可以说是标准的延续,或者说是民间的尝试,总之是这么一个非官方的东西
说到这里顺便提一下JCP组织也就是Java Community Process/Java社区
当初Sun公司在java发布之初,为了提倡开源和共项
同时也出于一个提出合理的标准的目的,而让广大的开发者参与标准的制定
而成立了这样一个社区,现在还健在,网址是jcp.org
每一个新的规范发布之前都会在这个社区广泛讨论,最终对规范的制定产生巨大的影响
其中就包括企业级的参与者,相当有名的JBoss以及我国的金碟公司都是其中的成员

下面介绍一下几个相当著名的框架,必须要指出的是,虽然框架大多开源 但并不代表所有的框架都开源,比如.net framework,但是java框架大多数开源
言归正传
Struts
表示层框架,名字来源于飞机的金属框架
可能有读者会提问了
表示层不是客户端么?
没错,但是语言这东西,众口烁金,别人都这么说你就不好不这么说了
最早表示层说的是客户端,后来随着时间的发展
人们也把服务器端直接与客户端//比如IE
打交道的那部分也称为表示层//JSP+Servlet
那么表示层框架是干什么的呢?
早先大规模应用JSP的时候,人们发现,JSP里面充斥着逻辑代码与数据
可读性极差,于是人们借用很早很早以前的MVC模式的思想
把表示层组件分为V-Viewer,也就是JSP
M-Model模型,一般来说是一个JavaBean
C-Controller控制器,一般来说是一个Servlet
所有人通过JSP和服务器打交道,发送请求,Viewer把这个请求转发给Controller
Controller通过调用一个Model来处理该请求,然后返回数据到Viewer
这么一个过程,从而达到数据与逻辑的剥离,增强代码可读性,降低维护成本
而帮助人们实现这一系列东西的就是Struts框架,就是这么一个东西
Struts的竞争对手主要是产商们极力倡导的JSF也就是Java Server Faces
但是由于Struts出道时间早,所以应用比较多
JSF则是产商们大力支持,前景看好
对于这一层来说,在JSP的html代码中出现的java语句越少越好
因为java代码越少说明页面处理的业务逻辑越少,也越合理
这也是Struts最初的目的,记住这话

Spring 大名鼎鼎的Spring框架
有人曾说2005年一片叫春之声,指的就是该框架
Spring起源于Rod Johnson的《Expert One-on-One J2EE Design and Development》一书
Rod Johnson认为,J2ee里面的那一套//尤其是ejb
太重了,对于单机的系统来说,没有必要使用那么复杂的东西
于是就开始设计并引导Spring小组开发出这样一个构架
不能不说他是个天才,因为的的确确不是所有的系统都是跨多服务器的
没有必要把一个简单的系统设计得那么复杂//天才的那几个共性又体现出来了
Spring从诞生之日起就是针对EJB的,力争在不少应用上取代EJB
而它也确实达到了这个目的
现在包括WebLogic等主流应用服务器还有主流IDE都开始逐渐接受该框架
并提供相应支持
提到Spring就不能不说控制反转Ioc//Inversion of Control
和依赖注射DI//Dependency Injection
什么叫控制反转呢?
套用好莱坞的一句名言就是:你呆着别动,到时我会找你。
什么意思呢?就好比一个皇帝和太监
有一天皇帝想幸某个美女,于是跟太监说,今夜我要宠幸美女
皇帝往往不会告诉太监,今晚几点会回宫,会回哪张龙床,他只会告诉太监他要哪位美女
其它一切都交由太监去安排,到了晚上皇帝回宫时,自然会有美女出现在皇帝的龙床上
这就是控制反转,而把美女送到皇帝的寝宫里面去就是注射
太监就是是框架里面的注射控制器类BeanFactory,负责找到美女并送到龙床上去
整个后宫可以看成是Spring框架,美女就是Spring控制下的JavaBean
而传统的模式就是一个饥渴男去找小姐出台
找领班,帮助给介绍一个云云,于是领班就开始给他张罗
介绍一个合适的给他,完事后,再把小姐还给领班,下次再来
这个过程中,领班就是查询上下文Context,领班的一个职能就是给客户找到他们所要的小姐
这就是lookup()方法,领班手中的小姐名录就是JNDI//Java Naming and Directory Interface
小姐就是EJB,饥渴男是客户端,青楼是EJB容器
看到区别了么?饥渴男去找小姐出台很麻烦,不仅得找,用完后还得把小姐给还回去
而皇帝爽翻了,什么都不用管,交给太监去处理,控制权转移到太监手中去了
而不是皇帝,必要时候由太监给注射进去就可以了
看到Spring的美妙了吧,Spring还提供了与多个主流框架的支持
可以和其它开源框架集成
Hibernate
名字取材自ORM最早的一句玩笑话//ORM就是OR-Mapping
说用了ORM之后,程序员就可以去冬眠了,而不需要操心那么多事
这里不得不说的是,该框架由于做得太好,以至于被J2ee招安,成为EJB3.0的一部分
替代原有EJB2.X里面关于Entity Bean而成为EJB ORM的工具
这里解释一下ORM//OR-Mapping
中文名对象关系映射
什么意思呢?我们知道传统的数据库都是关系型的
一条条记录以表格的形式储存,而表与表之间充斥着是关系/关联
比如说一个人,名字zhaoce,性别男,年龄23那么数据库中是这么储存的
姓名 性别 年龄 zhaoce m 23 某女 f 22
而实际应用服务器中的实体都是以对象的形式存在,一个个对象
zhaoce是以这种形式存在的
Human human=new Human();
human.setName(“zhaoce”)
human.setSex(“m”);
human.setAge(23);
这样的,那么我们知道,传统的JDBC是通过一个二维字符串将数据取出
需要我们自己将其包装成对象,在存入的时候,我们还需要将对象拆开
放入sql语句中//Insert into Huamn values(‘zhaoce’,’m’,23)
然后执行该sql语句
太麻烦太麻烦,ORM理念的提出改变了这一切,ORM认为,这些东西应该由框架来做
而不是程序员,程序员做他该做的,不要为这种破事分心,还测试半天
于是就出现了Hibernate,JDO,TopLink等等,甚至.net里面也有ADO.net
过去一段时间是Hibernate和JDO争风,现在看来Hibernate逐渐成为主流并被官方接纳
成为规范标准之一,替代掉原来EJB2.X的ORM EntityBean
TopLink则是Oracle公司推出和Oracle数据库结合的一种ORM
商业用软件,贵且复杂,不过正在逐渐开放
而象表示层一样,这一种专门面对数据层的代码也被称为数据持久层
所以数据持久层这一概念有时不仅仅指代数据库
关于ORM,最高的境界应该是在java代码中不出现任何一句的sql语句
注意,是不包括sql语句,Hibernate的hql以及ejb的ejb-ql不算在内
至于出现不出现hql/ejb-ql等替代ql,这要视具体情况而定,不过最好也是不出现
当然最后所说的过分理想的情况往往不现实,总之一句话
以sql为代表的ql/还有hql,ejbql等/语句在代码中出现得越少越好
记住这话,现在未必能够理解,学了以后就懂了

这三个是目前最为常用的框架 而目前光已公布的框架就>500
还在不停增加中,不可能一一列举,有兴趣的可以去看相应文档 要指出的是框架不是应用程序
只是一堆组件的有序复合,应用时不能脱离于应用服务器单独存在

九:收尾

最后一篇介绍几个常见的概念

设计模式
这可不仅是java独有
我看的书就是c++和smalltalk例子的
先说说什么是设计模式
模式是什么?模式是经验的总结,潜规则的抽象
什么意思呢?比如说我们坐飞机,上飞机前需要经过几个步骤
什么安检领取登机牌之类的,这一套流程能不能改呢?
可以,但为什么几乎全世界的航空公司登机前都是这一套流程呢?
因为航空公司经过长期实践之后得出了一堆结论和经验
并认为这样做才是最安全,或说是最有效率的
这就是模式,模式是编程高手之间交流的桥梁
两个编程高手通过统一命名的模式了解对方的思想
当然不借助模式可不可以?当然可以,只是模式无处不在,你不知道而已
又比如吃饭,每吃一口饭,我们要先端碗,拿筷子,张嘴,塞饭入口,咀嚼最后吞咽
这就是一套模式,我们给这套模式命名为吃饭
那么当老爸叫吃饭的时候,我们就能明白什么意思
而不用老爸进来呓呓啊啊并比画上半天,哑语也不是这么用的
这就是模式,已知的模式有400多种//好象更多,不记得了
比如数据库有数据库的设计模式,编程有编程的模式等等
面向对象有常用的21种模式,需要掌握,主要分为创建,行为,结构三类
J2ee有J2ee的模式,Sun公司出了一本书叫《J2EE核心模式》可以拿来看看
必需要指明的是,模式不是规范,比如吃饭模式
没有人规定你吃饭非得要那么吃,你可以端碗,上抛,张嘴在下落后连碗一起吞咽
这也可以,只要你愿意,同样,只要你愿意,你就可以不遵循模式
模式之外还有反模式,学模式不可定势,不要学死,活学活用,无招胜有招才是最高境界

JavaDoc
文档工具,极其好用
可以根据注释自动生成HTML文档

Ant
98年,有一位程序员在从欧洲飞回美国的飞机上想到了这么一个东西
从而改变了整个世界,他的名字叫James Duncan Davidson
组织管理工具,可以这么描述它
比如你想在编译之后自动再次生成JavaDoc
那么你只需要编辑Ant脚本//对,就像Windows脚本那样
然后批处理就可以了,不过现在Ant已经广泛集成到IDE中去
不需要自己手动编写,不过如果想要炫炫,据说此招百试不爽

JUnit
测试工具,Unit家族可不只有JUnit
还有其它版本的,这个不细说,具体实践一下就明白了

POJO
//Plain Old Java Object
就是传统的Java对象,也就是一个JavaBean
由虚拟机来掌握其生死
常用的两个管理构架/规范是Spring和EJB容器
命名由来是某人//名字我忘了
觉得我们使用了太多的规范,以至于我们都忘记了纯粹的java对象
以至于我们都忽略了它的存在,所以叫了这么一个名字
以唤醒人们的记忆,这个意义上来说EJB其实不能算是POJO
毕竟遵循了一堆的接口,但是不管怎样,接口归接口,还是没有继承类
没有被强加什么//遵循可以写空方法假遵循
所以说还是POJO也对
但是由于这种东西缺乏管理,不象Servlet有专门的容器管理并继承了一定的类
而没有管理的对象在虚拟机中是很危险的,因为垃圾回收机制各个虚拟机不同
而且也不怎样,极有可能长时间不回收,这样在企业级的应用中呢
就有可能造成内存大量被占用从而死机,毫无疑问,这种机制需要优化
这种优化就是通过EJB容器或者Spring构架来实现
这么做还有一个好处就是迫使程序员对每一个类做封装
强迫他做管理,以达到防止内存泄露的目的,内存泄露最经常出现的错误就是
引用未释放,引用最典型体现在new这个关键字上,new得越多引用得越多
随着时间地增长,有可能导致循环,不停new new new new new…..
其中哪怕只要有一个new处理不当,虚拟机无法回收内存
那就极有可能完蛋,而且这种小bug越是在大的项目越是难以找到
有可能因为一个人而影响整个项目组,所以不妨记住我的一条经验
好的系统框架不应该在业务逻辑流程中出现new关键字
现在不理解也无所谓,将来有一天会明白的

SOA
面向服务的构架
不说太多,这个属于上上层建筑
不过不妨记住我的一句话,可以帮助理解这个概念
面向什么就是对什么做封装
面向对象就是对对象做封装
面向服务类似,剩下的靠悟性

反射
1.4新增功能,非常强大
通过反射,程序可以解析出类本身的属性也就是变量
//注意这里说的属性不是.net里面的属性,我不喜欢微软造的新名词,乱
还有行为也就是方法,然后通过invoke()方法调用该方法
甚至可以新增对象等,java首创,本是其它语言所没有的
后来被微软抄了去,利用该功能,开源框架广泛受益并大量采用,近乎疯狂地使用
具体就不说了,最后要指出的是,有一种说法是利用反射会降低效率
在早期的时候,的确是,现在不会了,放心使用

容器
5.0以后的版本在J2SE中都出现了容器
各位甚至可以自己尝试用标准库去使用容器

推荐网站
www.javaeye.com //java视线论坛,Hibernate国内的权威
dev2dev.bea.com //bea的dev2dev社区,用WebLogic首选的好去处
www-128.ibm.com/developerworks //ibm developer works社区,ibm产品的老家
www.jdon.com //j道,Jboss国内相对讨论会多一点的地方,有自己的框架
www.matrix.org.cn //matrix,有自己的框架,很清新的论坛
jcp.org //JCP,前面说到过了
sourceforge.net //开源的东西几乎这里都可以找到,除java外还有游戏共享等
saloon.javaranch.com //我常去,人气不错
www.apache.org //阿帕奇老家
www.jboss.com //Jboss和Hibernate老家
www.springframework.org //Spring老家
www.wiki.org //非常好的百科站点,可惜国内被封,创始人加入了Eclipse zone
www.google.com //你要的这里有,不信?输入关键字再按一下那个靠左的白色按钮试试

书籍
《Thinking in Java》 //实话说,一般,尤其是翻译后的版本,原版还行
《Java教程》 //电子工业出版社出版的那本,上下册,很厚,但翻译得不错
《21天学通Java》 //入门极好,但是《21天学通j2ee》极烂,不要买
《Mastering EJB》 //翻译过的书质量我不清楚,估计不怎样,请看原版书籍
《精通Hibernate》 //看清楚作者,孙卫琴,其它人的别买

其它的可以不用了,网络上的远比书上来得多,来得好,虽然也来得杂

最后的建议
一,不要做一个浮躁的人
二,学好英语,很重要
三,阅读源代码和文档
四,共享源代码,不要做一个功利的人
五,热爱Java


版权声明:本文为CSDN博主「phphot」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/phphot/article/details/2171421

Contents

    1. Collections:   List, Dictionary, Set, Tuple, Range, Enumerate, Iterator, Generator.
    2. Types:            Type, String, Regular_Exp, Format, Numbers, Combinatorics, Datetime.
    3. Syntax:           Args, Inline, Closure, Decorator, Class, Duck_Types, Enum, Exceptions.
    4. System:          Print, Input, Command_Line_Arguments, Open, Path, Command_Execution.
    5. Data:               JSON, Pickle, CSV, SQLite, Bytes, Struct, Array, MemoryView, Deque.
    6. Advanced:     Threading, Operator, Introspection, Metaprograming, Eval, Coroutine.
    7. Libraries:        Progress_Bar, Plot, Table, Curses, Logging, Scraping, Web, Profile,
                                  NumPy, Image, Animation, Audio.

Main

1
2
if __name__ == '__main__':     # Runs main() if file wasn't imported.
main()

List

1
<list> = <list>[from_inclusive : to_exclusive : ±step_size]
1
2
<list>.append(<el>)            # Or: <list> += [<el>]
<list>.extend(<collection>) # Or: <list> += <collection>
1
2
3
4
<list>.sort()
<list>.reverse()
<list> = sorted(<collection>)
<iter> = reversed(<list>)
1
2
3
4
5
6
7
sum_of_elements  = sum(<collection>)
elementwise_sum = [sum(pair) for pair in zip(list_a, list_b)]
sorted_by_second = sorted(<collection>, key=lambda el: el[1])
sorted_by_both = sorted(<collection>, key=lambda el: (el[1], el[0]))
flatter_list = list(itertools.chain.from_iterable(<list>))
product_of_elems = functools.reduce(lambda out, x: out * x, <collection>)
list_of_chars = list(<str>)
1
2
3
4
5
6
<int> = <list>.count(<el>)     # Returns number of occurrences. Also works on strings.
index = <list>.index(<el>) # Returns index of first occurrence or raises ValueError.
<list>.insert(index, <el>) # Inserts item at index and moves the rest to the right.
<el> = <list>.pop([index]) # Removes and returns item at index or from the end.
<list>.remove(<el>) # Removes first occurrence of item or raises ValueError.
<list>.clear() # Removes all items. Also works on dictionary and set.

Dictionary

1
2
3
<view> = <dict>.keys()                          # Coll. of keys that reflects changes.
<view> = <dict>.values() # Coll. of values that reflects changes.
<view> = <dict>.items() # Coll. of key-value tuples that reflects chgs.
1
2
3
4
value  = <dict>.get(key, default=None)          # Returns default if key is missing.
value = <dict>.setdefault(key, default=None) # Returns and writes default if key is missing.
<dict> = collections.defaultdict(<type>) # Creates a dict with default value of type.
<dict> = collections.defaultdict(lambda: 1) # Creates a dict with default value 1.
1
2
3
<dict> = dict(<collection>)                     # Creates a dict from coll. of key-value pairs.
<dict> = dict(zip(keys, values)) # Creates a dict from two collections.
<dict> = dict.fromkeys(keys [, value]) # Creates a dict from collection of keys.
1
2
3
4
<dict>.update(<dict>)                           # Adds items. Replaces ones with matching keys.
value = <dict>.pop(key) # Removes item or raises KeyError.
{k for k, v in <dict>.items() if v == value} # Returns set of keys that point to the value.
{k: v for k, v in <dict>.items() if k in keys} # Returns dictionary filtered by keys.

Counter

1
2
3
4
5
6
7
>>> from collections import Counter
>>> colors = ['blue', 'red', 'blue', 'red', 'blue']
>>> counter = Counter(colors)
>>> counter['yellow'] += 1
Counter({'blue': 3, 'red': 2, 'yellow': 1})
>>> counter.most_common()[0]
('blue', 3)

Set

1
<set> = set()
1
2
<set>.add(<el>)                                 # Or: <set> |= {<el>}
<set>.update(<collection>) # Or: <set> |= <set>
1
2
3
4
5
6
<set>  = <set>.union(<coll.>)                   # Or: <set> | <set>
<set> = <set>.intersection(<coll.>) # Or: <set> & <set>
<set> = <set>.difference(<coll.>) # Or: <set> - <set>
<set> = <set>.symmetric_difference(<coll.>) # Or: <set> ^ <set>
<bool> = <set>.issubset(<coll.>) # Or: <set> <= <set>
<bool> = <set>.issuperset(<coll.>) # Or: <set> >= <set>
1
2
3
<el> = <set>.pop()                              # Raises KeyError if empty.
<set>.remove(<el>) # Raises KeyError if missing.
<set>.discard(<el>) # Doesn't raise an error.

Frozen Set

  • Is immutable and hashable.
  • That means it can be used as a key in a dictionary or as an element in a set.
1
<frozenset> = frozenset(<collection>)

Tuple

Tuple is an immutable and hashable list.

1
2
3
<tuple> = ()
<tuple> = (<el>, )
<tuple> = (<el_1>, <el_2> [, ...])

Named Tuple

Tuple’s subclass with named elements.

1
2
3
4
5
6
7
8
9
10
11
12
>>> from collections import namedtuple
>>> Point = namedtuple('Point', 'x y')
>>> p = Point(1, y=2)
Point(x=1, y=2)
>>> p[0]
1
>>> p.x
1
>>> getattr(p, 'y')
2
>>> p._fields # Or: Point._fields
('x', 'y')

Range

1
2
3
<range> = range(to_exclusive)
<range> = range(from_inclusive, to_exclusive)
<range> = range(from_inclusive, to_exclusive, ±step_size)
1
2
from_inclusive = <range>.start
to_exclusive = <range>.stop

Enumerate

1
2
for i, el in enumerate(<collection> [, i_start]):
...

Iterator

1
2
3
4
<iter> = iter(<collection>)                 # `iter(<iter>)` returns unmodified iterator.
<iter> = iter(<function>, to_exclusive) # A sequence of return values until 'to_exclusive'.
<el> = next(<iter> [, default]) # Raises StopIteration or returns 'default' on end.
<list> = list(<iter>) # Returns a list of iterator's remaining elements.

Itertools

1
from itertools import count, repeat, cycle, chain, islice
1
2
3
<iter> = count(start=0, step=1)             # Returns incremented value endlessly.
<iter> = repeat(<el> [, times]) # Returns element endlessly or 'times' times.
<iter> = cycle(<collection>) # Repeats the sequence endlessly.
1
2
<iter> = chain(<coll_1>, <coll_2> [, ...])  # Empties collections in order.
<iter> = chain.from_iterable(<collection>) # Empties collections inside a collection in order.
1
2
<iter> = islice(<collection>, to_exclusive)
<iter> = islice(<collection>, from_inclusive, to_exclusive [, +step_size])

Generator

  • Any function that contains a yield statement returns a generator.
  • Generators and iterators are interchangeable.
1
2
3
4
def count(start, step):
while True:
yield start
start += step
1
2
3
>>> counter = count(10, 2)
>>> next(counter), next(counter), next(counter)
(10, 12, 14)

Type

  • Everything is an object.
  • Every object has a type.
  • Type and class are synonymous.
1
2
<type> = type(<el>)                          # Or: <el>.__class__
<bool> = isinstance(<el>, <type>) # Or: issubclass(type(<el>), <type>)
1
2
>>> type('a'), 'a'.__class__, str
(<class 'str'>, <class 'str'>, <class 'str'>)

Some types do not have built-in names, so they must be imported:

1
from types import FunctionType, MethodType, LambdaType, GeneratorType

ABC

An abstract base class introduces virtual subclasses, that don’t inherit from it but are still recognized by isinstance() and issubclass().

1
2
3
>>> from collections.abc import Sequence, Collection, Iterable
>>> isinstance([1, 2, 3], Iterable)
True
1
2
3
4
5
6
7
+------------------+------------+------------+------------+
| | Sequence | Collection | Iterable |
+------------------+------------+------------+------------+
| list, range, str | yes | yes | yes |
| dict, set | | yes | yes |
| iter | | | yes |
+------------------+------------+------------+------------+
1
2
3
>>> from numbers import Integral, Rational, Real, Complex, Number
>>> isinstance(123, Number)
True
1
2
3
4
5
6
7
8
9
+--------------------+----------+----------+----------+----------+----------+
| | Integral | Rational | Real | Complex | Number |
+--------------------+----------+----------+----------+----------+----------+
| int | yes | yes | yes | yes | yes |
| fractions.Fraction | | yes | yes | yes | yes |
| float | | | yes | yes | yes |
| complex | | | | yes | yes |
| decimal.Decimal | | | | | yes |
+--------------------+----------+----------+----------+----------+----------+

String

1
2
<str>  = <str>.strip()                       # Strips all whitespace characters from both ends.
<str> = <str>.strip('<chars>') # Strips all passed characters from both ends.
1
2
3
4
<list> = <str>.split()                       # Splits on one or more whitespace characters.
<list> = <str>.split(sep=None, maxsplit=-1) # Splits on 'sep' str at most 'maxsplit' times.
<list> = <str>.splitlines(keepends=False) # Splits on line breaks. Keeps them if 'keepends'.
<str> = <str>.join(<coll_of_strings>) # Joins elements using string as separator.
1
2
3
4
5
<bool> = <sub_str> in <str>                  # Checks if string contains a substring.
<bool> = <str>.startswith(<sub_str>) # Pass tuple of strings for multiple options.
<bool> = <str>.endswith(<sub_str>) # Pass tuple of strings for multiple options.
<int> = <str>.find(<sub_str>) # Returns start index of first match or -1.
<int> = <str>.index(<sub_str>) # Same but raises ValueError if missing.
1
2
<str>  = <str>.replace(old, new [, count])   # Replaces 'old' with 'new' at most 'count' times.
<str> = <str>.translate(<table>) # Use `str.maketrans(<dict>)` to generate table.
1
2
<str>  = chr(<int>)                          # Converts int to unicode char.
<int> = ord(<str>) # Converts unicode char to int.
  • Also: 'lstrip()', 'rstrip()'.
  • Also: 'lower()', 'upper()', 'capitalize()' and 'title()'.

Property Methods

1
2
3
4
5
6
7
8
9
+---------------+----------+----------+----------+----------+----------+
| | [ !#$%…] | [a-zA-Z] | [¼½¾] | [²³¹] | [0-9] |
+---------------+----------+----------+----------+----------+----------+
| isprintable() | yes | yes | yes | yes | yes |
| isalnum() | | yes | yes | yes | yes |
| isnumeric() | | | yes | yes | yes |
| isdigit() | | | | yes | yes |
| isdecimal() | | | | | yes |
+---------------+----------+----------+----------+----------+----------+
  • Also: 'isspace()' checks for '[ \t\n\r\f\v…]'.

Regex

1
2
3
4
5
6
7
import re
<str> = re.sub(<regex>, new, text, count=0) # Substitutes all occurrences with 'new'.
<list> = re.findall(<regex>, text) # Returns all occurrences as strings.
<list> = re.split(<regex>, text, maxsplit=0) # Use brackets in regex to keep the matches.
<Match> = re.search(<regex>, text) # Searches for first occurrence of the pattern.
<Match> = re.match(<regex>, text) # Searches only at the beginning of the text.
<iter> = re.finditer(<regex>, text) # Returns all occurrences as match objects.
  • Search() and match() return None if they can’t find a match.
  • Argument 'flags=re.IGNORECASE' can be used with all functions.
  • Argument 'flags=re.MULTILINE' makes '^' and '$' match the start/end of each line.
  • Argument 'flags=re.DOTALL' makes dot also accept newline.
  • Use r'\1' or '\\1' for backreference.
  • Add '?' after an operator to make it non-greedy.

Match Object

1
2
3
4
5
<str>   = <Match>.group()                      # Returns whole match. Also group(0).
<str> = <Match>.group(1) # Returns part in first bracket.
<tuple> = <Match>.groups() # Returns all bracketed parts.
<int> = <Match>.start() # Returns start index of a match.
<int> = <Match>.end() # Returns exclusive end index of a match.

Special Sequences

  • By default digits, whitespaces and alphanumerics from all alphabets are matched, unless 'flags=re.ASCII' argument is used.
  • Use capital letter for negation.
1
2
3
'\d' == '[0-9]'                                # Matches any digit.
'\w' == '[a-zA-Z0-9_]' # Matches any alphanumeric.
'\s' == '[ \t\n\r\f\v]' # Matches any whitespace.

Format

1
2
<str> = f'{<el_1>}, {<el_2>}'
<str> = '{}, {}'.format(<el_1>, <el_2>)

Attributes

1
2
3
4
5
6
7
>>> from collections import namedtuple
>>> Person = namedtuple('Person', 'name height')
>>> person = Person('Jean-Luc', 187)
>>> f'{person.height}'
'187'
>>> '{p.height}'.format(p=person)
'187'

General Options

1
2
3
{<el>:<10}                                     # '<el>      '
{<el>:^10} # ' <el> '
{<el>:>10} # ' <el>'
1
2
{<el>:.<10}                                    # '<el>......'
{<el>:<0} # '<el>'

Strings

'!r' calls object’s repr() method, instead of str(), to get a string.

1
2
3
{'abcde'!r:<10}                                # "'abcde'   "
{'abcde':.3} # 'abc'
{'abcde':10.3} # 'abc '

Numbers

1
2
3
4
5
6
{ 123456:10,}                                  # '   123,456'
{ 123456:10_} # ' 123_456'
{ 123456:+10} # ' +123456'
{-123456:=10} # '- 123456'
{ 123456: } # ' 123456'
{-123456: } # '-123456'

Floats

1
2
3
4
{1.23456:10.3}                                 # '      1.23'
{1.23456:10.3f} # ' 1.235'
{1.23456:10.3e} # ' 1.235e+00'
{1.23456:10.3%} # ' 123.456%'

Comparison of float presentation types:

1
2
3
4
5
6
7
8
9
10
11
12
+---------------+-----------------+-----------------+-----------------+-----------------+
| | {<float>} | {<float>:f} | {<float>:e} | {<float>:%} |
+---------------+-----------------+-----------------+-----------------+-----------------+
| 0.000056789 | '5.6789e-05' | '0.000057' | '5.678900e-05' | '0.005679%' |
| 0.00056789 | '0.00056789' | '0.000568' | '5.678900e-04' | '0.056789%' |
| 0.0056789 | '0.0056789' | '0.005679' | '5.678900e-03' | '0.567890%' |
| 0.056789 | '0.056789' | '0.056789' | '5.678900e-02' | '5.678900%' |
| 0.56789 | '0.56789' | '0.567890' | '5.678900e-01' | '56.789000%' |
| 5.6789 | '5.6789' | '5.678900' | '5.678900e+00' | '567.890000%' |
| 56.789 | '56.789' | '56.789000' | '5.678900e+01' | '5678.900000%' |
| 567.89 | '567.89' | '567.890000' | '5.678900e+02' | '56789.000000%' |
+---------------+-----------------+-----------------+-----------------+-----------------+
1
2
3
4
5
6
7
8
9
10
11
12
+---------------+-----------------+-----------------+-----------------+-----------------+
| | {<float>:.2} | {<float>:.2f} | {<float>:.2e} | {<float>:.2%} |
+---------------+-----------------+-----------------+-----------------+-----------------+
| 0.000056789 | '5.7e-05' | '0.00' | '5.68e-05' | '0.01%' |
| 0.00056789 | '0.00057' | '0.00' | '5.68e-04' | '0.06%' |
| 0.0056789 | '0.0057' | '0.01' | '5.68e-03' | '0.57%' |
| 0.056789 | '0.057' | '0.06' | '5.68e-02' | '5.68%' |
| 0.56789 | '0.57' | '0.57' | '5.68e-01' | '56.79%' |
| 5.6789 | '5.7' | '5.68' | '5.68e+00' | '567.89%' |
| 56.789 | '5.7e+01' | '56.79' | '5.68e+01' | '5678.90%' |
| 567.89 | '5.7e+02' | '567.89' | '5.68e+02' | '56789.00%' |
+---------------+-----------------+-----------------+-----------------+-----------------+

Ints

1
2
3
{90:c}                                # 'Z'
{90:b} # '1011010'
{90:X} # '5A'

Numbers

Types

1
2
3
4
5
<int>      = int(<float/str/bool>)    # Or: math.floor(<float>)
<float> = float(<int/str/bool>) # Or: <real>e±<int>
<complex> = complex(real=0, imag=0) # Or: <real> ± <real>j
<Fraction> = fractions.Fraction(numerator=0, denominator=1)
<Decimal> = decimal.Decimal(<str/int/float>)
  • 'int(<str>)' and 'float(<str>)' raise ValueError on malformed strings.
  • Decimal numbers can be represented exactly, unlike floats where '1.1 + 2.2 != 3.3'.
  • Precision of decimal operations is set with: 'decimal.getcontext().prec = <int>'.

Basic Functions

1
2
3
<num> = pow(<num>, <num>)             # Or: <num> ** <num>
<num> = abs(<num>) # <float> = abs(<complex>)
<num> = round(<num> [, ±ndigits]) # `round(126, -1) == 130`

Math

1
2
3
from math import e, pi, inf, nan
from math import cos, acos, sin, asin, tan, atan, degrees, radians
from math import log, log10, log2

Statistics

1
from statistics import mean, median, variance, pvariance, pstdev

Random

1
2
3
4
5
from random import random, randint, choice, shuffle
<float> = random()
<int> = randint(from_inclusive, to_inclusive)
<el> = choice(<list>)
shuffle(<list>)

Bin, Hex

1
2
3
4
<int>     = 0b<bin>                   # Or: 0x<hex>
<int> = int('<bin>', 2) # Or: int('<hex>', 16)
<int> = int('0b<bin>', 0) # Or: int('0x<hex>', 0)
'0b<bin>' = bin(<int>) # Or: '0x<hex>' = hex(<int>)

Bitwise Operators

1
2
3
4
5
6
<int>     = <int> & <int>             # And
<int> = <int> | <int> # Or
<int> = <int> ^ <int> # Xor (0 if both bits equal)
<int> = <int> << n_bits # Shift left
<int> = <int> >> n_bits # Shift right
<int> = ~<int> # Compliment (flips bits)

Combinatorics

  • Every function returns an iterator.
  • If you want to print the iterator, you need to pass it to the list() function!
1
from itertools import product, combinations, combinations_with_replacement, permutations
1
2
3
>>> product([0, 1], repeat=3)
[(0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1),
(1, 0, 0), (1, 0, 1), (1, 1, 0), (1, 1, 1)]
1
2
3
>>> product('ab', '12')
[('a', '1'), ('a', '2'),
('b', '1'), ('b', '2')]
1
2
>>> combinations('abc', 2)
[('a', 'b'), ('a', 'c'), ('b', 'c')]
1
2
3
4
>>> combinations_with_replacement('abc', 2)
[('a', 'a'), ('a', 'b'), ('a', 'c'),
('b', 'b'), ('b', 'c'),
('c', 'c')]
1
2
3
4
>>> permutations('abc', 2)
[('a', 'b'), ('a', 'c'),
('b', 'a'), ('b', 'c'),
('c', 'a'), ('c', 'b')]

Datetime

  • Module ‘datetime’ provides ‘date’ <D>, ‘time’ <T>, ‘datetime’ <DT> and ‘timedelta’ <TD> classes. All are immutable and hashable.
  • Time and datetime can be ‘aware’ <a>, meaning they have defined timezone, or ‘naive’ <n>, meaning they don’t.
  • If object is naive, it is presumed to be in the system’s timezone.
1
2
from datetime import date, time, datetime, timedelta
from dateutil.tz import UTC, tzlocal, gettz, resolve_imaginary

Constructors

1
2
3
4
5
<D>  = date(year, month, day)
<T> = time(hour=0, minute=0, second=0, microsecond=0, tzinfo=None, fold=0)
<DT> = datetime(year, month, day, hour=0, minute=0, second=0, ...)
<TD> = timedelta(days=0, seconds=0, microseconds=0, milliseconds=0,
minutes=0, hours=0, weeks=0)
  • Use '<D/DT>.weekday()' to get the day of the week (Mon == 0).
  • 'fold=1' means second pass in case of time jumping back for one hour.
  • '<DTa> = resolve_imaginary(<DTa>)' fixes DTs that fall into missing hour.

Now

1
2
3
<D/DTn>  = D/DT.today()                     # Current local date or naive datetime.
<DTn> = DT.utcnow() # Naive datetime from current UTC time.
<DTa> = DT.now(<tzinfo>) # Aware datetime from current tz time.
  • To extract time use '<DTn>.time()', '<DTa>.time()' or '<DTa>.timetz()'.

Timezone

1
2
3
4
5
<tzinfo> = UTC                              # UTC timezone. London without DST.
<tzinfo> = tzlocal() # Local timezone. Also gettz().
<tzinfo> = gettz('<Cont.>/<City>') # 'Continent/City_Name' timezone or None.
<DTa> = <DT>.astimezone(<tzinfo>) # Datetime, converted to passed timezone.
<Ta/DTa> = <T/DT>.replace(tzinfo=<tzinfo>) # Unconverted object with new timezone.

Encode

1
2
3
4
5
<D/T/DT> = D/T/DT.fromisoformat('<iso>')    # Object from ISO string. Raises ValueError.
<DT> = DT.strptime(<str>, '<format>') # Datetime from str, according to format.
<D/DTn> = D/DT.fromordinal(<int>) # D/DTn from days since Christ, at midnight.
<DTn> = DT.fromtimestamp(<real>) # Local time DTn from seconds since Epoch.
<DTa> = DT.fromtimestamp(<real>, <tz.>) # Aware datetime from seconds since Epoch.
  • ISO strings come in following forms: 'YYYY-MM-DD', 'HH:MM:SS.ffffff[±<offset>]', or both separated by an arbitrary character. Offset is formatted as: 'HH:MM'.
  • On Unix systems Epoch is '1970-01-01 00:00 UTC', '1970-01-01 01:00 CET', …

Decode

1
2
3
4
5
<str>    = <D/T/DT>.isoformat(sep='T')      # Also timespec='auto/hours/minutes/seconds'.
<str> = <D/T/DT>.strftime('<format>') # Custom string representation.
<int> = <D/DT>.toordinal() # Days since Christ, ignoring time and tz.
<float> = <DTn>.timestamp() # Seconds since Epoch from DTn in local time.
<float> = <DTa>.timestamp() # Seconds since Epoch from DTa.

Format

1
2
3
4
>>> from datetime import datetime
>>> dt = datetime.strptime('2015-05-14 23:39:00.00 +0200', '%Y-%m-%d %H:%M:%S.%f %z')
>>> dt.strftime("%A, %dth of %B '%y, %I:%M%p %Z")
"Thursday, 14th of May '15, 11:39PM UTC+02:00"
  • When parsing, '%z' also accepts '±HH:MM'.
  • For abbreviated weekday and month use '%a' and '%b'.

Arithmetics

1
2
3
4
<D/DT>   = <D/DT>   ± <TD>                  # Returned datetime can fall into missing hour.
<TD> = <D/DTn> - <D/DTn> # Returns the difference, ignoring time jumps.
<TD> = <DTa> - <DTa> # Ignores time jumps if they share tzinfo object.
<TD> = <DT_UTC> - <DT_UTC> # Convert DTs to UTC to get the actual delta.

Arguments

Inside Function Call

1
2
3
<function>(<positional_args>)                  # f(0, 0)
<function>(<keyword_args>) # f(x=0, y=0)
<function>(<positional_args>, <keyword_args>) # f(0, y=0)

Inside Function Definition

1
2
3
def f(<nondefault_args>):                      # def f(x, y):
def f(<default_args>): # def f(x=0, y=0):
def f(<nondefault_args>, <default_args>): # def f(x, y=0):

Splat Operator

Inside Function Call

Splat expands a collection into positional arguments, while splatty-splat expands a dictionary into keyword arguments.

1
2
3
args   = (1, 2)
kwargs = {'x': 3, 'y': 4, 'z': 5}
func(*args, **kwargs)

Is the same as:

1
func(1, 2, x=3, y=4, z=5)

Inside Function Definition

Splat combines zero or more positional arguments into a tuple, while splatty-splat combines zero or more keyword arguments into a dictionary.

1
2
def add(*a):
return sum(a)
1
2
>>> add(1, 2, 3)
6
1
2
3
4
def f(x, y, z):                # f(x=1, y=2, z=3) | f(1, y=2, z=3) | f(1, 2, z=3) | f(1, 2, 3)
def f(*, x, y, z): # f(x=1, y=2, z=3)
def f(x, *, y, z): # f(x=1, y=2, z=3) | f(1, y=2, z=3)
def f(x, y, *, z): # f(x=1, y=2, z=3) | f(1, y=2, z=3) | f(1, 2, z=3)
1
2
3
4
def f(*args):                  # f(1, 2, 3)
def f(x, *args): # f(1, 2, 3)
def f(*args, z): # f(1, 2, z=3)
def f(x, *args, z): # f(1, 2, z=3)
1
2
3
def f(**kwargs):               # f(x=1, y=2, z=3)
def f(x, **kwargs): # f(x=1, y=2, z=3) | f(1, y=2, z=3)
def f(*, x, **kwargs): # f(x=1, y=2, z=3)
1
2
3
4
def f(*args, **kwargs):        # f(x=1, y=2, z=3) | f(1, y=2, z=3) | f(1, 2, z=3) | f(1, 2, 3)
def f(x, *args, **kwargs): # f(x=1, y=2, z=3) | f(1, y=2, z=3) | f(1, 2, z=3) | f(1, 2, 3)
def f(*args, y, **kwargs): # f(x=1, y=2, z=3) | f(1, y=2, z=3)
def f(x, *args, z, **kwargs): # f(x=1, y=2, z=3) | f(1, y=2, z=3) | f(1, 2, z=3)

Other Uses

1
2
3
4
<list>  = [*<collection> [, ...]]
<set> = {*<collection> [, ...]}
<tuple> = (*<collection>, [...])
<dict> = {**<dict> [, ...]}
1
head, *body, tail = <collection>

Inline

Lambda

1
2
<function> = lambda: <return_value>
<function> = lambda <argument_1>, <argument_2>: <return_value>

Comprehension

1
2
3
4
<list> = [i+1 for i in range(10)]                   # [1, 2, ..., 10]
<set> = {i for i in range(10) if i > 5} # {6, 7, 8, 9}
<iter> = (i+5 for i in range(10)) # (5, 6, ..., 14)
<dict> = {i: i*2 for i in range(10)} # {0: 0, 1: 2, ..., 9: 18}
1
out = [i+j for i in range(10) for j in range(10)]

Is the same as:

1
2
3
4
out = []
for i in range(10):
for j in range(10):
out.append(i+j)

Map, Filter, Reduce

1
2
3
4
from functools import reduce
<iter> = map(lambda x: x + 1, range(10)) # (1, 2, ..., 10)
<iter> = filter(lambda x: x > 5, range(10)) # (6, 7, 8, 9)
<obj> = reduce(lambda out, x: out + x, range(10)) # 45

Any, All

1
2
<bool> = any(<collection>)                          # False if empty.
<bool> = all(el[1] for el in <collection>) # True if empty.

If - Else

1
<obj> = <expression_if_true> if <condition> else <expression_if_false>
1
2
>>> [a if a else 'zero' for a in (0, 1, 2, 3)]
['zero', 1, 2, 3]

Namedtuple, Enum, Dataclass

1
2
3
from collections import namedtuple
Point = namedtuple('Point', 'x y')
point = Point(0, 0)
1
2
3
from enum import Enum
Direction = Enum('Direction', 'n e s w')
direction = Direction.n
1
2
3
from dataclasses import make_dataclass
Creature = make_dataclass('Creature', ['location', 'direction'])
creature = Creature(Point(0, 0), Direction.n)

Closure

We have a closure in Python when:

  • A nested function references a value of its enclosing function and then
  • the enclosing function returns the nested function.
1
2
3
4
def get_multiplier(a):
def out(b):
return a * b
return out
1
2
3
>>> multiply_by_3 = get_multiplier(3)
>>> multiply_by_3(10)
30
  • If multiple nested functions within enclosing function reference the same value, that value gets shared.
  • To dynamically access function’s first free variable use '<function>.__closure__[0].cell_contents'.

Partial

1
2
from functools import partial
<function> = partial(<function> [, <arg_1>, <arg_2>, ...])
1
2
3
4
>>> import operator as op
>>> multiply_by_3 = partial(op.mul, 3)
>>> multiply_by_3(10)
30
  • Partial is also useful in cases when function needs to be passed as an argument, because it enables us to set its arguments beforehand.
  • A few examples being: 'defaultdict(<function>)', 'iter(<function>, to_exclusive)' and dataclass’s 'field(default_factory=<function>)'.

Nonlocal

If variable is being assigned to anywhere in the scope, it is regarded as a local variable, unless it is declared as a ‘global’ or a ‘nonlocal’.

1
2
3
4
5
6
7
def get_counter():
i = 0
def out():
nonlocal i
i += 1
return i
return out
1
2
3
>>> counter = get_counter()
>>> counter(), counter(), counter()
(1, 2, 3)

Decorator

A decorator takes a function, adds some functionality and returns it.

1
2
3
@decorator_name
def function_that_gets_passed_to_decorator():
...

Debugger Example

Decorator that prints function’s name every time it gets called.

1
2
3
4
5
6
7
8
9
10
11
12
from functools import wraps

def debug(func):
@wraps(func)
def out(*args, **kwargs):
print(func.__name__)
return func(*args, **kwargs)
return out

@debug
def add(x, y):
return x + y
  • Wraps is a helper decorator that copies the metadata of the passed function (func) to the function it is wrapping (out).
  • Without it 'add.__name__' would return 'out'.

LRU Cache

Decorator that caches function’s return values. All function’s arguments must be hashable.

1
2
3
4
5
from functools import lru_cache

@lru_cache(maxsize=None)
def fib(n):
return n if n < 2 else fib(n-2) + fib(n-1)
  • CPython interpreter limits recursion depth to 1000 by default. To increase it use 'sys.setrecursionlimit(<depth>)'.

Parametrized Decorator

A decorator that accepts arguments and returns a normal decorator that accepts a function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from functools import wraps

def debug(print_result=False):
def decorator(func):
@wraps(func)
def out(*args, **kwargs):
result = func(*args, **kwargs)
print(func.__name__, result if print_result else '')
return result
return out
return decorator

@debug(print_result=True)
def add(x, y):
return x + y

Class

1
2
3
4
5
6
7
8
9
10
11
12
class <name>:
def __init__(self, a):
self.a = a
def __repr__(self):
class_name = self.__class__.__name__
return f'{class_name}({self.a!r})'
def __str__(self):
return str(self.a)

@classmethod
def get_class_name(cls):
return cls.__name__
  • Return value of repr() should be unambiguous and of str() readable.
  • If only repr() is defined, it will also be used for str().

Str() use cases:

1
2
3
4
5
print(<el>)
print(f'{<el>}')
raise Exception(<el>)
loguru.logger.debug(<el>)
csv.writer(<file>).writerow([<el>])

Repr() use cases:

1
2
3
4
5
print([<el>])
print(f'{<el>!r}')
>>> <el>
loguru.logger.exception()
Z = dataclasses.make_dataclass('Z', ['a']); print(Z(<el>))

Constructor Overloading

1
2
3
class <name>:
def __init__(self, a=None):
self.a = a

Inheritance

1
2
3
4
5
6
7
8
9
class Person:
def __init__(self, name, age):
self.name = name
self.age = age

class Employee(Person):
def __init__(self, name, age, staff_num):
super().__init__(name, age)
self.staff_num = staff_num

Multiple Inheritance

1
2
3
class A: pass
class B: pass
class C(A, B): pass

MRO determines the order in which parent classes are traversed when searching for a method:

1
2
>>> C.mro()
[<class 'C'>, <class 'A'>, <class 'B'>, <class 'object'>]

Property

Pythonic way of implementing getters and setters.

1
2
3
4
5
6
7
8
class MyClass:
@property
def a(self):
return self._a

@a.setter
def a(self, value):
self._a = value
1
2
3
4
>>> el = MyClass()
>>> el.a = 123
>>> el.a
123

Dataclass

Decorator that automatically generates init(), repr() and eq() special methods.

1
2
3
4
5
6
7
from dataclasses import dataclass, field

@dataclass(order=False, frozen=False)
class <class_name>:
<attr_name_1>: <type>
<attr_name_2>: <type> = <default_value>
<attr_name_3>: list/dict/set = field(default_factory=list/dict/set)
  • Objects can be made sortable with 'order=True' and/or immutable and hashable with 'frozen=True'.
  • Function field() is needed because '<attr_name>: list = []' would make a list that is shared among all instances.
  • Default_factory can be any callable.

Inline:

1
2
3
4
from dataclasses import make_dataclass
<class> = make_dataclass('<class_name>', <coll_of_attribute_names>)
<class> = make_dataclass('<class_name>', <coll_of_tuples>)
<tuple> = ('<attr_name>', <type> [, <default_value>])

Slots

Mechanism that restricts objects to attributes listed in ‘slots’ and significantly reduces their memory footprint.

1
2
3
4
class MyClassWithSlots:
__slots__ = ['a']
def __init__(self):
self.a = 1

Copy

1
2
3
from copy import copy, deepcopy
<object> = copy(<object>)
<object> = deepcopy(<object>)

Duck Types

A duck type is an implicit type that prescribes a set of special methods. Any object that has those methods defined is considered a member of that duck type.

Comparable

  • If eq() method is not overridden, it returns 'id(self) == id(other)', which is the same as 'self is other'.
  • That means all objects compare not equal by default.
  • Only the left side object has eq() method called, unless it returns NotImplemented, in which case the right object is consulted.
1
2
3
4
5
6
7
class MyComparable:
def __init__(self, a):
self.a = a
def __eq__(self, other):
if isinstance(other, type(self)):
return self.a == other.a
return NotImplemented

Hashable

  • Hashable object needs both hash() and eq() methods and its hash value should never change.
  • Hashable objects that compare equal must have the same hash value, meaning default hash() that returns 'id(self)' will not do.
  • That is why Python automatically makes classes unhashable if you only implement eq().
1
2
3
4
5
6
7
8
9
10
11
12
class MyHashable:
def __init__(self, a):
self._a = copy.deepcopy(a)
@property
def a(self):
return self._a
def __eq__(self, other):
if isinstance(other, type(self)):
return self.a == other.a
return NotImplemented
def __hash__(self):
return hash(self.a)

Sortable

  • With total_ordering decorator you only need to provide eq() and one of lt(), gt(), le() or ge() special methods.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from functools import total_ordering

@total_ordering
class MySortable:
def __init__(self, a):
self.a = a
def __eq__(self, other):
if isinstance(other, type(self)):
return self.a == other.a
return NotImplemented
def __lt__(self, other):
if isinstance(other, type(self)):
return self.a < other.a
return NotImplemented

Iterator

  • Any object that has methods next() and iter() is an iterator.
  • Next() should return next item or raise StopIteration.
  • Iter() should return ‘self’.
1
2
3
4
5
6
7
8
class Counter:
def __init__(self):
self.i = 0
def __next__(self):
self.i += 1
return self.i
def __iter__(self):
return self
1
2
3
>>> counter = Counter()
>>> next(counter), next(counter), next(counter)
(1, 2, 3)

Python has many different iterator objects:

Callable

  • All functions and classes have a call() method, hence are callable.
  • When this cheatsheet uses '<function>' for an argument, it actually means '<callable>'.
1
2
3
4
5
6
class Counter:
def __init__(self):
self.i = 0
def __call__(self):
self.i += 1
return self.i
1
2
3
>>> counter = Counter()
>>> counter(), counter(), counter()
(1, 2, 3)

Context Manager

  • Enter() should lock the resources and return an object.
  • Exit() should release the resources.
  • Any exception that happens inside the with block is passed to the exit() method.
  • If it wishes to suppress the exception it must return a true value.
1
2
3
4
5
6
7
8
class MyOpen():
def __init__(self, filename):
self.filename = filename
def __enter__(self):
self.file = open(self.filename)
return self.file
def __exit__(self, exc_type, exc_value, traceback):
self.file.close()
1
2
3
4
5
>>> with open('test.txt', 'w') as file:
... file.write('Hello World!')
>>> with MyOpen('test.txt') as file:
... print(file.read())
Hello World!

Iterable Duck Types

Iterable

  • Only required method is iter(). It should return an iterator of object’s items.
  • Contains() automatically works on any object that has iter() defined.
1
2
3
4
5
6
class MyIterable:
def __init__(self, a):
self.a = a
def __iter__(self):
for el in self.a:
yield el
1
2
3
4
5
>>> z = MyIterable([1, 2, 3])
>>> iter(z)
<generator object MyIterable.__iter__>
>>> 1 in z
True

Collection

  • Only required methods are iter() and len().
  • This cheatsheet actually means '<iterable>' when it uses '<collection>'.
  • I chose not to use the name ‘iterable’ because it sounds scarier and more vague than ‘collection’.
1
2
3
4
5
6
7
8
9
class MyCollection:
def __init__(self, a):
self.a = a
def __iter__(self):
return iter(self.a)
def __contains__(self, el):
return el in self.a
def __len__(self):
return len(self.a)

Sequence

  • Only required methods are len() and getitem().
  • Getitem() should return an item at index or raise IndexError.
  • Iter() and contains() automatically work on any object that has getitem() defined.
  • Reversed() automatically works on any object that has getitem() and len() defined.
1
2
3
4
5
6
7
8
9
10
11
12
13
class MySequence:
def __init__(self, a):
self.a = a
def __iter__(self):
return iter(self.a)
def __contains__(self, el):
return el in self.a
def __len__(self):
return len(self.a)
def __getitem__(self, i):
return self.a[i]
def __reversed__(self):
return reversed(self.a)

Collections.abc.Sequence

  • It’s a richer interface than the basic sequence.
  • Extending it generates iter(), contains(), reversed(), index(), and count().
  • Unlike 'abc.Iterable' and 'abc.Collection', it is not a duck type. That is why 'issubclass(MySequence, collections.abc.Sequence)' would return False even if MySequence had all the methods defined.
1
2
3
4
5
6
7
class MyAbcSequence(collections.abc.Sequence):
def __init__(self, a):
self.a = a
def __len__(self):
return len(self.a)
def __getitem__(self, i):
return self.a[i]

Table of required and automatically available special methods:

1
2
3
4
5
6
7
8
9
10
11
+------------+------------+------------+------------+--------------+
| | Iterable | Collection | Sequence | abc.Sequence |
+------------+------------+------------+------------+--------------+
| iter() | REQ | REQ | yes | yes |
| contains() | yes | yes | yes | yes |
| len() | | REQ | REQ | REQ |
| getitem() | | | REQ | REQ |
| reversed() | | | yes | yes |
| index() | | | | yes |
| count() | | | | yes |
+------------+------------+------------+------------+--------------+
  • Other ABCs that generate missing methods are: MutableSequence, Set, MutableSet, Mapping and MutableMapping.
  • Names of their required methods are stored in '<abc>.__abstractmethods__'.

Enum

1
2
3
4
5
6
from enum import Enum, auto

class <enum_name>(Enum):
<member_name_1> = <value_1>
<member_name_2> = <value_2_a>, <value_2_b>
<member_name_3> = auto()
  • If there are no numeric values before auto(), it returns 1.
  • Otherwise it returns an increment of the last numeric value.
1
2
3
4
5
<member> = <enum>.<member_name>                # Returns a member.
<member> = <enum>['<member_name>'] # Returns a member or raises KeyError.
<member> = <enum>(<value>) # Returns a member or raises ValueError.
<str> = <member>.name # Returns member's name.
<obj> = <member>.value # Returns member's value.
1
2
3
4
list_of_members = list(<enum>)
member_names = [a.name for a in <enum>]
member_values = [a.value for a in <enum>]
random_member = random.choice(list(<enum>))
1
2
3
4
def get_next_member(member):
members = list(member.__class__)
index = (members.index(member) + 1) % len(members)
return members[index]

Inline

1
2
3
Cutlery = Enum('Cutlery', ['fork', 'knife', 'spoon'])
Cutlery = Enum('Cutlery', 'fork knife spoon')
Cutlery = Enum('Cutlery', {'fork': 1, 'knife': 2, 'spoon': 3})

Functions can not be values, so they must be wrapped:

1
2
3
from functools import partial
LogicOp = Enum('LogicOp', {'AND': partial(lambda l, r: l and r),
'OR' : partial(lambda l, r: l or r)})
  • Another solution in this particular case, is to use 'and_' and 'or_' functions from module operator.

Exceptions

Basic Example

1
2
3
4
try:
<code>
except <exception>:
<code>

Complex Example

1
2
3
4
5
6
7
8
9
10
try:
<code_1>
except <exception_a>:
<code_2_a>
except <exception_b>:
<code_2_b>
else:
<code_2_c>
finally:
<code_3>

Catching Exceptions

1
2
3
4
except <exception>:
except <exception> as <name>:
except (<exception>, ...):
except (<exception>, ...) as <name>:
  • Also catches subclasses of the exception.

Raising Exceptions

1
2
3
raise <exception>
raise <exception>()
raise <exception>(<el_1> [, ...])

Re-raising caught exception:

1
2
3
except <exception>:
<code>
raise

Useful built-in exceptions:

1
2
3
raise ValueError('Argument is of right type but inappropriate value!')
raise TypeError('Argument is of wrong type!')
raise RuntimeError('None of above!')

Common Built-in Exceptions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
BaseException
+-- SystemExit # Raised by the sys.exit() function.
+-- KeyboardInterrupt # Raised when the user hits the interrupt key (ctrl-c).
+-- Exception # User-defined exceptions should be derived from this class.
+-- StopIteration # Raised by next() when run on an empty iterator.
+-- ArithmeticError # Base class for arithmetic errors.
| +-- ZeroDivisionError # Raised when dividing by zero.
+-- AttributeError # Raised when an attribute is missing.
+-- EOFError # Raised by input() when it hits end-of-file condition.
+-- LookupError # Raised when a look-up on a collection fails.
| +-- IndexError # Raised when a sequence index is out of range.
| +-- KeyError # Raised when a dictionary key or set element is not found.
+-- NameError # Raised when a variable name is not found.
+-- OSError # Failures such as “file not found” or “disk full”.
| +-- FileNotFoundError # When a file or directory is requested but doesn't exist.
+-- RuntimeError # Raised by errors that don't fall in other categories.
| +-- RecursionError # Raised when the the maximum recursion depth is exceeded.
+-- TypeError # Raised when an argument is of wrong type.
+-- ValueError # When an argument is of right type but inappropriate value.
+-- UnicodeError # Raised when encoding/decoding strings from/to bytes fails.

Collections and their exceptions:

1
2
3
4
5
6
7
8
+-----------+------------+------------+------------+
| | list | dict | set |
+-----------+------------+------------+------------+
| getitem() | IndexError | KeyError | |
| pop() | IndexError | KeyError | KeyError |
| remove() | ValueError | | KeyError |
| index() | ValueError | | |
+-----------+------------+------------+------------+

User-defined Exceptions

1
2
3
4
5
class MyError(Exception):
pass

class MyInputError(MyError):
pass

Print

1
print(<el_1>, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
  • Use 'file=sys.stderr' for errors.
  • Use 'flush=True' to forcibly flush the stream.

Pretty Print

1
2
from pprint import pprint
pprint(<collection>, width=80, depth=None)
  • Levels deeper than ‘depth’ get replaced by ‘…’.

Input

Reads a line from user input or pipe if present.

1
<str> = input(prompt=None)
  • Trailing newline gets stripped.
  • Prompt string is printed to the standard output before reading input.
  • Raises EOFError when user hits EOF (ctrl-d) or input stream gets exhausted.

Command Line Arguments

1
2
3
import sys
script_name = sys.argv[0]
arguments = sys.argv[1:]

Argparse

1
2
3
4
5
6
7
8
9
from argparse import ArgumentParser, FileType
p = ArgumentParser(description=<str>)
p.add_argument('-<short_name>', '--<name>', action='store_true') # Flag
p.add_argument('-<short_name>', '--<name>', type=<type>) # Option
p.add_argument('<name>', type=<type>, nargs=1) # First argument
p.add_argument('<name>', type=<type>, nargs='+') # Remaining arguments
p.add_argument('<name>', type=<type>, nargs='*') # Optional arguments
args = p.parse_args() # Exits on error.
value = args.<name>
  • Use 'help=<str>' to set argument description.
  • Use 'default=<el>' to set the default value.
  • Use 'type=FileType(<mode>)' for files.

Open

Opens the file and returns a corresponding file object.

1
<file> = open('<path>', mode='r', encoding=None, newline=None)
  • 'encoding=None' means that the default encoding is used, which is platform dependent. Best practice is to use 'encoding="utf-8"' whenever possible.
  • 'newline=None' means all different end of line combinations are converted to ‘\n’ on read, while on write all ‘\n’ characters are converted to system’s default line separator.
  • 'newline=""' means no conversions take place, but input is still broken into chunks by readline() and readlines() on either ‘\n’, ‘\r’ or ‘\r\n’.

Modes

  • 'r' - Read (default).
  • 'w' - Write (truncate).
  • 'x' - Write or fail if the file already exists.
  • 'a' - Append.
  • 'w+' - Read and write (truncate).
  • 'r+' - Read and write from the start.
  • 'a+' - Read and write from the end.
  • 't' - Text mode (default).
  • 'b' - Binary mode.

Exceptions

  • 'FileNotFoundError' can be risen when reading with 'r' or 'r+'.
  • 'FileExistsError' can be risen when writing with 'x'.
  • 'IsADirectoryError' and 'PermissionError' can be risen by any.
  • 'OSError' is the parent class of all listed exceptions.

File Object

1
2
3
4
<file>.seek(0)                      # Moves to the start of the file.
<file>.seek(offset) # Moves 'offset' chars/bytes from the start.
<file>.seek(0, 2) # Moves to the end of the file.
<bin_file>.seek(±offset, <anchor>) # Anchor: 0 start, 1 current pos., 2 end.
1
2
3
4
<str/bytes> = <file>.read(size=-1)  # Reads 'size' chars/bytes or until EOF.
<str/bytes> = <file>.readline() # Returns a line or empty string/bytes on EOF.
<list> = <file>.readlines() # Returns a list of remaining lines.
<str/bytes> = next(<file>) # Returns a line using buffer. Do not mix.
1
2
3
<file>.write(<str/bytes>)           # Writes a string or bytes object.
<file>.writelines(<collection>) # Writes a coll. of strings or bytes objects.
<file>.flush() # Flushes write buffer.
  • Methods do not add or strip trailing newlines, even writelines().

Read Text from File

1
2
3
def read_file(filename):
with open(filename, encoding='utf-8') as file:
return file.readlines()

Write Text to File

1
2
3
def write_to_file(filename, text):
with open(filename, 'w', encoding='utf-8') as file:
file.write(text)

Path

1
2
from os import getcwd, path, listdir
from glob import glob
1
2
3
<str>  = getcwd()                   # Returns the current working directory.
<str> = path.join(<path>, ...) # Joins two or more pathname components.
<str> = path.abspath(<path>) # Return an absolute path.
1
2
3
<str>  = path.basename(<path>)      # Returns final component.
<str> = path.dirname(<path>) # Returns path without final component.
<tup.> = path.splitext(<path>) # Splits on last period of final component.
1
2
<list> = listdir(path='.')          # Returns filenames located at path.
<list> = glob('<pattern>') # Returns paths matching the wildcard pattern.
1
2
3
<bool> = path.exists(<path>)        # Or: <Path>.exists()
<bool> = path.isfile(<path>) # Or: <DirEntry/Path>.is_file()
<bool> = path.isdir(<path>) # Or: <DirEntry/Path>.is_dir()

DirEntry

Using scandir() instead of listdir() can significantly increase the performance of code that also needs file type information.

1
from os import scandir
1
2
3
4
<iter> = scandir(path='.')          # Returns DirEntry objects located at path.
<str> = <DirEntry>.path # Returns path as a string.
<str> = <DirEntry>.name # Returns final component as a string.
<file> = open(<DirEntry>) # Opens the file and returns a file object.

Path Object

1
from pathlib import Path
1
2
<Path> = Path(<path> [, ...])       # Accepts strings, Paths and DirEntry objects.
<Path> = <path> / <path> [/ ...] # One of the paths must be a Path object.
1
2
3
<Path> = Path()                     # Returns relative cwd. Also Path('.').
<Path> = Path.cwd() # Returns absolute cwd. Also Path().resolve().
<Path> = <Path>.resolve() # Returns absolute Path without symlinks.
1
2
3
4
5
<Path> = <Path>.parent              # Returns Path without final component.
<str> = <Path>.name # Returns final component as a string.
<str> = <Path>.stem # Returns final component without extension.
<str> = <Path>.suffix # Returns final component's extension.
<tup.> = <Path>.parts # Returns all components as strings.
1
2
<iter> = <Path>.iterdir()           # Returns dir contents as Path objects.
<iter> = <Path>.glob('<pattern>') # Returns Paths matching the wildcard pattern.
1
2
<str>  = str(<Path>)                # Returns path as a string.
<file> = open(<Path>) # Opens the file and returns a file object.

OS Commands

Files and Directories

  • Paths can be either strings, Paths, or DirEntry objects.
  • Functions report OS related errors by raising either OSError or one of its subclasses.
1
import os, shutil
1
2
os.chdir(<path>)                    # Changes current working directory.
os.mkdir(<path>, mode=0o777) # Creates a directory. Mode is in octal.
1
2
shutil.copy(from, to)               # Copies the file.
shutil.copytree(from, to) # Copies the directory.
1
2
os.rename(from, to)                 # Renames the file or directory.
os.replace(from, to) # Same, but overwrites 'to' if it exists.
1
2
3
os.remove(<path>)                   # Deletes the file.
os.rmdir(<path>) # Deletes empty directory.
shutil.rmtree(<path>) # Deletes non-empty directory.

Shell Commands

1
2
import os
<str> = os.popen('<shell_command>').read()

Using subprocess:

1
2
3
4
5
6
>>> import subprocess, shlex
>>> a = subprocess.run(shlex.split('ls -a'), stdout=subprocess.PIPE)
>>> a.stdout
b'.\n..\nfile1.txt\nfile2.txt\n'
>>> a.returncode
0

JSON

Text file format for storing collections of strings and numbers.

1
2
3
import json
<str> = json.dumps(<object>, ensure_ascii=True, indent=None)
<object> = json.loads(<str>)

Read Object from JSON File

1
2
3
def read_json_file(filename):
with open(filename, encoding='utf-8') as file:
return json.load(file)

Write Object to JSON File

1
2
3
def write_to_json_file(filename, an_object):
with open(filename, 'w', encoding='utf-8') as file:
json.dump(an_object, file, ensure_ascii=False, indent=2)

Pickle

Binary file format for storing objects.

1
2
3
import pickle
<bytes> = pickle.dumps(<object>)
<object> = pickle.loads(<bytes>)

Read Object from File

1
2
3
def read_pickle_file(filename):
with open(filename, 'rb') as file:
return pickle.load(file)

Write Object to File

1
2
3
def write_to_pickle_file(filename, an_object):
with open(filename, 'wb') as file:
pickle.dump(an_object, file)

CSV

Text file format for storing spreadsheets.

1
import csv

Read

1
2
3
<reader> = csv.reader(<file>, dialect='excel', delimiter=',')
<list> = next(<reader>) # Returns next row as a list of strings.
<list> = list(<reader>) # Returns list of remaining rows.
  • File must be opened with 'newline=""' argument, or newlines embedded inside quoted fields will not be interpreted correctly!

Write

1
2
3
<writer> = csv.writer(<file>, dialect='excel', delimiter=',')
<writer>.writerow(<collection>) # Encodes objects using `str(<el>)`.
<writer>.writerows(<coll_of_coll>) # Appends multiple rows.
  • File must be opened with 'newline=""' argument, or an extra ‘\r’ will be added on platforms that use ‘\r\n’ linendings!

Parameters

  • 'dialect' - Master parameter that sets the default values.
  • 'delimiter' - A one-character string used to separate fields.
  • 'quotechar' - Character for quoting fields that contain special characters.
  • 'doublequote' - Whether quotechars inside fields get doubled or escaped.
  • 'skipinitialspace' - Whether whitespace after delimiter gets stripped.
  • 'lineterminator' - How does writer terminate lines.
  • 'quoting' - Controls the amount of quoting: 0 - as necessary, 1 - all.
  • 'escapechar' - Character for escaping ‘quotechar’ if ‘doublequote’ is false.

Dialects

1
2
3
4
5
6
7
8
9
10
11
+------------------+--------------+--------------+--------------+
| | excel | excel-tab | unix |
+------------------+--------------+--------------+--------------+
| delimiter | ',' | '\t' | ',' |
| quotechar | '"' | '"' | '"' |
| doublequote | True | True | True |
| skipinitialspace | False | False | False |
| lineterminator | '\r\n' | '\r\n' | '\n' |
| quoting | 0 | 0 | 1 |
| escapechar | None | None | None |
+------------------+--------------+--------------+--------------+

Read Rows from CSV File

1
2
3
def read_csv_file(filename):
with open(filename, encoding='utf-8', newline='') as file:
return list(csv.reader(file))

Write Rows to CSV File

1
2
3
4
def write_to_csv_file(filename, rows):
with open(filename, 'w', encoding='utf-8', newline='') as file:
writer = csv.writer(file)
writer.writerows(rows)

SQLite

Server-less database engine that stores each database into separate file.

Connect

Opens a connection to the database file. Creates a new file if path doesn’t exist.

1
2
3
4
import sqlite3
db = sqlite3.connect('<path>') # Also ':memory:'.
...
db.close()

Read

Returned values can be of type str, int, float, bytes or None.

1
2
3
<cursor> = db.execute('<query>')                # Can raise sqlite3.OperationalError.
<tuple> = <cursor>.fetchone() # Returns next row. Also next(<cursor>).
<list> = <cursor>.fetchall() # Returns remaining rows.

Write

1
2
db.execute('<query>')
db.commit()

Or:

1
2
with db:
db.execute('<query>')

Placeholders

  • Passed values can be of type str, int, float, bytes, None, bool, datetime.date or datetime.datetme.
  • Bools will be stored and returned as ints and dates as ISO formatted strings.
1
2
3
db.execute('<query>', <list/tuple>)             # Replaces '?'s in query with values.
db.execute('<query>', <dict/namedtuple>) # Replaces ':<key>'s with values.
db.executemany('<query>', <coll_of_above>) # Runs execute() many times.

Example

In this example values are not actually saved because 'db.commit()' is omitted!

1
2
3
4
5
>>> db = sqlite3.connect('test.db')
>>> db.execute('create table t (a, b, c)')
>>> db.execute('insert into t values (1, 2, 3)')
>>> db.execute('select * from t').fetchall()
[(1, 2, 3)]

MySQL

Has a very similar interface, with differences listed below.

1
2
3
4
5
6
7
# $ pip3 install mysql-connector
from mysql import connector
db = connector.connect(host=<str>, user=<str>, password=<str>, database=<str>)
<cursor> = db.cursor()
<cursor>.execute('<query>') # Only cursor has execute method.
<cursor>.execute('<query>', <list/tuple>) # Replaces '%s's in query with values.
<cursor>.execute('<query>', <dict/namedtuple>) # Replaces '%(<key>)s's with values.

Bytes

Bytes object is an immutable sequence of single bytes. Mutable version is called bytearray.

1
2
3
4
<bytes> = b'<str>'                       # Only accepts ASCII characters and \x00 - \xff.
<int> = <bytes>[<index>] # Returns int in range from 0 to 255.
<bytes> = <bytes>[<slice>] # Returns bytes even if it has only one element.
<bytes> = <bytes>.join(<coll_of_bytes>) # Joins elements using bytes object as separator.

Encode

1
2
3
4
<bytes> = bytes(<coll_of_ints>)          # Ints must be in range from 0 to 255.
<bytes> = bytes(<str>, 'utf-8') # Or: <str>.encode('utf-8')
<bytes> = <int>.to_bytes(n_bytes, byteorder='big/little', signed=False)
<bytes> = bytes.fromhex('<hex>')

Decode

1
2
3
4
<list>  = list(<bytes>)                  # Returns ints in range from 0 to 255.
<str> = str(<bytes>, 'utf-8') # Or: <bytes>.decode('utf-8')
<int> = int.from_bytes(<bytes>, byteorder='big/little', signed=False)
'<hex>' = <bytes>.hex()

Read Bytes from File

1
2
3
def read_bytes(filename):
with open(filename, 'rb') as file:
return file.read()

Write Bytes to File

1
2
3
def write_bytes(filename, bytes_obj):
with open(filename, 'wb') as file:
file.write(bytes_obj)

Struct

  • Module that performs conversions between a sequence of numbers and a bytes object.
  • Machine’s native type sizes and byte order are used by default.
1
2
3
4
from struct import pack, unpack, iter_unpack
<bytes> = pack('<format>', <num_1> [, <num_2>, ...])
<tuple> = unpack('<format>', <bytes>)
<tuples> = iter_unpack('<format>', <bytes>)

Example

1
2
3
4
>>> pack('>hhl', 1, 2, 3)
b'\x00\x01\x00\x02\x00\x00\x00\x03'
>>> unpack('>hhl', b'\x00\x01\x00\x02\x00\x00\x00\x03')
(1, 2, 3)

Format

For standard sizes start format string with:

  • '=' - native byte order
  • '<' - little-endian
  • '>' - big-endian (also '!')

Integer types. Use capital letter for unsigned type. Standard sizes are in brackets:

  • 'x' - pad byte
  • 'b' - char (1)
  • 'h' - short (2)
  • 'i' - int (4)
  • 'l' - long (4)
  • 'q' - long long (8)

Floating point types:

  • 'f' - float (4)
  • 'd' - double (8)

Array

List that can only hold numbers of a predefined type. Available types and their sizes in bytes are listed above.

1
2
3
4
from array import array
<array> = array('<typecode>', <collection>) # Array from coll. of numbers.
<array> = array('<typecode>', <bytes>) # Array from bytes object.
<bytes> = bytes(<array>) # Or: <array>.tobytes()

Memory View

  • A sequence object that points to the memory of another object.
  • Each element can reference a single or multiple consecutive bytes, depending on format.
  • Order and number of elements can be changed with slicing.
1
2
3
4
5
<mview> = memoryview(<bytes/bytearray/array>)  # Immutable if bytes, else mutable.
<real> = <mview>[<index>] # Returns an int or a float.
<mview> = <mview>[<slice>] # Mview with rearranged elements.
<mview> = <mview>.cast('<typecode>') # Casts memoryview to the new format.
<mview>.release() # Releases the object's memory buffer.

Decode

1
2
3
4
5
6
7
<bin_file>.write(<mview>)                      # Appends mview to the binary file.
<bytes> = bytes(<mview>) # Creates a new bytes object.
<bytes> = <bytes>.join(<coll_of_mviews>) # Joins mviews using bytes object as sep.
<list> = list(<mview>) # Returns list of ints or floats.
<str> = str(<mview>, 'utf-8') # Treats mview as a seqence of bytes.
<int> = int.from_bytes(<mview>, byteorder='big/little', signed=False)
'<hex>' = <mview>.hex()

Deque

A thread-safe list with efficient appends and pops from either side. Pronounced “deck”.

1
2
from collections import deque
<deque> = deque(<collection>, maxlen=None)
1
2
3
4
<deque>.appendleft(<el>)                       # Opposite element is dropped if full.
<el> = <deque>.popleft() # Raises IndexError if empty.
<deque>.extendleft(<collection>) # Collection gets reversed.
<deque>.rotate(n=1) # Rotates elements to the right.

Threading

  • CPython interpreter can only run a single thread at a time.
  • That is why using multiple threads won’t result in a faster execution, unless there is an I/O operation in the thread.
1
from threading import Thread, RLock

Thread

1
2
3
4
5
thread = Thread(target=<function>, args=(<first_arg>, ))
thread.start()
...
<bool> = thread.is_alive() # Checks if thread has finished executing.
thread.join() # Waits for thread to finish.
  • Use 'kwargs=<dict>' to pass keyword arguments to the function.
  • Use 'daemon=True', or the program will not be able to exit while the thread is alive.

Lock

1
2
3
4
lock = RLock()
lock.acquire() # Waits for lock to be available.
...
lock.release()

Or:

1
2
3
lock = RLock()
with lock:
...

Thread Pool Executor

1
2
3
4
5
from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor(max_workers=None) as executor:
<iter> = executor.map(lambda x: x + 1, range(3)) # (1, 2, 3)
<iter> = executor.map(lambda x, y: x + y, 'abc', '123') # ('a1', 'b2', 'c3')
<Future> = executor.submit(<function> [, <arg_1>, ...])

Future:

1
2
<bool> = <Future>.done()             # Checks if thread has finished executing.
<obj> = <Future>.result() # Waits for thread to finish and returns result.

Queue

A thread-safe FIFO queue. For LIFO queue use LifoQueue.

1
2
from queue import Queue
<Queue> = Queue(maxsize=0)
1
2
3
4
<Queue>.put(<el>)                    # Blocks until queue stops being full.
<Queue>.put_nowait(<el>) # Raises queue.Full exception if full.
<el> = <Queue>.get() # Blocks until queue stops being empty.
<el> = <Queue>.get_nowait() # Raises _queue.Empty exception if empty.

Operator

Module of functions that provide the functionality of operators.

1
2
3
4
from operator import add, sub, mul, truediv, floordiv, mod, pow, neg, abs
from operator import eq, ne, lt, le, gt, ge
from operator import and_, or_, not_
from operator import itemgetter, attrgetter, methodcaller
1
2
3
4
5
6
import operator as op
sorted_by_second = sorted(<collection>, key=op.itemgetter(1))
sorted_by_both = sorted(<collection>, key=op.itemgetter(1, 0))
product_of_elems = functools.reduce(op.mul, <collection>)
LogicOp = enum.Enum('LogicOp', {'AND': op.and_, 'OR' : op.or_})
last_el = op.methodcaller('pop')(<list>)

Introspection

Inspecting code at runtime.

Variables

1
2
3
<list> = dir()                       # Returns names of variables in current scope.
<dict> = locals() # Returns dict of local variables. Also vars().
<dict> = globals() # Returns dict of global variables.

Attributes

1
2
3
4
<dict> = vars(<object>)
<bool> = hasattr(<object>, '<attr_name>')
value = getattr(<object>, '<attr_name>')
setattr(<object>, '<attr_name>', value)

Parameters

1
2
3
4
from inspect import signature
<sig> = signature(<function>)
no_of_params = len(<sig>.parameters)
param_names = list(<sig>.parameters.keys())

Metaprograming

Code that generates code.

Type

Type is the root class. If only passed an object it returns its type (class). Otherwise it creates a new class.

1
<class> = type('<class_name>', <parents_tuple>, <attributes_dict>)
1
2
>>> Z = type('Z', (), {'a': 'abcde', 'b': 12345})
>>> z = Z()

Meta Class

Class that creates classes.

1
2
3
def my_meta_class(name, parents, attrs):
attrs['a'] = 'abcde'
return type(name, parents, attrs)

Or:

1
2
3
4
class MyMetaClass(type):
def __new__(cls, name, parents, attrs):
attrs['a'] = 'abcde'
return type.__new__(cls, name, parents, attrs)
  • New() is a class method that gets called before init(). If it returns an instance of its class, then that instance gets passed to init() as a ‘self’ argument.
  • It receives the same arguments as init(), except for the first one that specifies the desired type of the returned instance (MyMetaClass in our case).
  • Like in our case, new() can also be called directly, usually from a new() method of a child class (def __new__(cls): return super().__new__(cls)).
  • The only difference between the examples above is that my_meta_class() returns a class of type type, while MyMetaClass() returns a class of type MyMetaClass.

Metaclass Attribute

Right before a class is created it checks if it has a ‘metaclass’ attribute defined. If not, it recursively checks if any of his parents has it defined and eventually comes to type().

1
2
class MyClass(metaclass=MyMetaClass):
b = 12345
1
2
>>> MyClass.a, MyClass.b
('abcde', 12345)

Type Diagram

1
2
type(MyClass)     == MyMetaClass     # MyClass is an instance of MyMetaClass.
type(MyMetaClass) == type # MyMetaClass is an instance of type.
1
2
3
4
5
6
7
8
9
+-------------+-------------+
| Classes | Metaclasses |
+-------------+-------------|
| MyClass --> MyMetaClass |
| | v |
| object -----> type <+ |
| | ^ +---+ |
| str ---------+ |
+-------------+-------------+

Inheritance Diagram

1
2
MyClass.__base__     == object       # MyClass is a subclass of object.
MyMetaClass.__base__ == type # MyMetaClass is a subclass of type.
1
2
3
4
5
6
7
8
9
+-------------+-------------+
| Classes | Metaclasses |
+-------------+-------------|
| MyClass | MyMetaClass |
| v | v |
| object <----- type |
| ^ | |
| str | |
+-------------+-------------+

Eval

1
2
3
4
5
6
7
>>> from ast import literal_eval
>>> literal_eval('1 + 2')
3
>>> literal_eval('[1, 2, 3]')
[1, 2, 3]
>>> literal_eval('abs(1)')
ValueError: malformed node or string

Coroutine

  • Any function that contains a '(yield)' expression returns a coroutine.
  • Coroutines are similar to iterators, but data needs to be pulled out of an iterator by calling 'next(<iter>)', while we push data into the coroutine by calling '<coroutine>.send(<el>)'.
  • Coroutines provide more powerful data routing possibilities than iterators.

Helper Decorator

  • All coroutines must first be “primed” by calling 'next(<coroutine>)'.
  • Remembering to call next() is easy to forget.
  • Solved by wrapping coroutine functions with the following decorator:
1
2
3
4
5
6
def coroutine(func):
def out(*args, **kwargs):
cr = func(*args, **kwargs)
next(cr)
return cr
return out

Pipeline Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def reader(target):
for i in range(10):
target.send(i)
target.close()

@coroutine
def adder(target):
while True:
value = (yield)
target.send(value + 100)

@coroutine
def printer():
while True:
value = (yield)
print(value, end=' ')
1
2
>>> reader(adder(printer()))
100 101 102 103 104 105 106 107 108 109

Libraries

Progress Bar

1
2
3
4
5
# $ pip3 install tqdm
from tqdm import tqdm
from time import sleep
for el in tqdm([1, 2, 3]):
sleep(0.2)

Plot

1
2
3
4
5
6
7
8
# $ pip3 install matplotlib
from matplotlib import pyplot
pyplot.plot(<y_data> [, label=<str>])
pyplot.plot(<x_data>, <y_data>)
pyplot.legend() # Adds a legend.
pyplot.savefig(<filename>) # Saves the figure.
pyplot.show() # Displays the figure.
pyplot.clf() # Clears the figure.

Table

Prints a CSV file as an ASCII table:

1
2
3
4
5
6
7
# $ pip3 install tabulate
import csv, tabulate
with open('test.csv', encoding='utf-8', newline='') as file:
rows = csv.reader(file)
header = [a.title() for a in next(rows)]
table = tabulate.tabulate(rows, header)
print(table)

Curses

Clears the terminal, prints a message and waits for an ESC key press:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from curses import wrapper, curs_set, ascii
from curses import KEY_UP, KEY_RIGHT, KEY_DOWN, KEY_LEFT

def main():
wrapper(draw)

def draw(screen):
curs_set(0) # Makes cursor invisible.
screen.nodelay(True) # Makes getch() non-blocking.
screen.clear()
screen.addstr(0, 0, 'Press ESC to quit.')
while screen.getch() != ascii.ESC:
pass

def get_border(screen):
from collections import namedtuple
P = namedtuple('P', 'x y')
height, width = screen.getmaxyx()
return P(width - 1, height - 1)

if __name__ == '__main__':
main()

Logging

1
2
# $ pip3 install loguru
from loguru import logger
1
2
3
logger.add('debug_{time}.log', colorize=True)  # Connects a log file.
logger.add('error_{time}.log', level='ERROR') # Another file for errors or higher.
logger.<level>('A logging message.')
  • Levels: 'debug', 'info', 'success', 'warning', 'error', 'critical'.

Exceptions

Exception description, stack trace and values of variables are appended automatically.

1
2
3
4
try:
...
except <exception>:
logger.exception('An error happened.')

Rotation

Argument that sets a condition when a new log file is created.

1
rotation=<int>|<datetime.timedelta>|<datetime.time>|<str>
  • '<int>' - Max file size in bytes.
  • '<timedelta>' - Max age of a file.
  • '<time>' - Time of day.
  • '<str>' - Any of above as a string: '100 MB', '1 month', 'monday at 12:00', …

Retention

Sets a condition which old log files get deleted.

1
retention=<int>|<datetime.timedelta>|<str>
  • '<int>' - Max number of files.
  • '<timedelta>' - Max age of a file.
  • '<str>' - Max age as a string: '1 week, 3 days', '2 months', …

Scraping

Scrapes Python’s URL, version number and logo from Wikipedia page:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# $ pip3 install requests beautifulsoup4
import requests
from bs4 import BeautifulSoup
URL = 'https://en.wikipedia.org/wiki/Python_(programming_language)'
try:
html = requests.get(URL).text
doc = BeautifulSoup(html, 'html.parser')
table = doc.find('table', class_='infobox vevent')
rows = table.find_all('tr')
link = rows[11].find('a')['href']
ver = rows[6].find('div').text.split()[0]
url_i = rows[0].find('img')['src']
image = requests.get(f'https:{url_i}').content
with open('test.png', 'wb') as file:
file.write(image)
print(link, ver)
except requests.exceptions.ConnectionError:
print("You've got problems with connection.")

Web

1
2
3
# $ pip3 install bottle
from bottle import run, route, static_file, template, post, request, response
import json

Run

1
2
run(host='localhost', port=8080)        # Runs locally.
run(host='0.0.0.0', port=80) # Runs globally.

Static Request

1
2
3
@route('/img/<image>')
def send_image(image):
return static_file(image, 'img_dir/', mimetype='image/png')

Dynamic Request

1
2
3
@route('/<sport>')
def send_page(sport):
return template('<h1>{{title}}</h1>', title=sport)

REST Request

1
2
3
4
5
6
7
@post('/odds/<sport>')
def odds_handler(sport):
team = request.forms.get('team')
home_odds, away_odds = 2.44, 3.29
response.headers['Content-Type'] = 'application/json'
response.headers['Cache-Control'] = 'no-cache'
return json.dumps([team, home_odds, away_odds])

Test:

1
2
3
4
5
6
7
# $ pip3 install requests
>>> import requests
>>> url = 'http://localhost:8080/odds/football'
>>> data = {'team': 'arsenal f.c.'}
>>> response = requests.post(url, data=data)
>>> response.json()
['arsenal f.c.', 2.44, 3.29]

Profiling

Stopwatch

1
2
3
4
from time import time
start_time = time() # Seconds since the Epoch.
...
duration = time() - start_time

High performance:

1
2
3
4
from time import perf_counter
start_time = perf_counter() # Seconds since restart.
...
duration = perf_counter() - start_time

Timing a Snippet

1
2
3
4
>>> from timeit import timeit
>>> timeit('"-".join(str(a) for a in range(100))',
... number=10000, globals=globals(), setup='pass')
0.34986

Profiling by Line

1
2
3
4
5
6
# $ pip3 install line_profiler memory_profiler
@profile
def main():
a = [*range(10000)]
b = {*range(10000)}
main()
1
2
3
4
5
6
7
$ kernprof -lv test.py
Line # Hits Time Per Hit % Time Line Contents
=======================================================
1 @profile
2 def main():
3 1 1128.0 1128.0 27.4 a = [*range(10000)]
4 1 2994.0 2994.0 72.6 b = {*range(10000)}
1
2
3
4
5
6
7
$ python3 -m memory_profiler test.py
Line # Mem usage Increment Line Contents
=======================================================
1 35.387 MiB 35.387 MiB @profile
2 def main():
3 35.734 MiB 0.348 MiB a = [*range(10000)]
4 36.160 MiB 0.426 MiB b = {*range(10000)}

Call Graph

Generates a PNG image of a call graph with highlighted bottlenecks:

1
2
3
4
5
6
7
8
# $ pip3 install pycallgraph
from pycallgraph import output, PyCallGraph
from datetime import datetime
time_str = datetime.now().strftime('%Y%m%d%H%M%S')
filename = f'profile-{time_str}.png'
drawer = output.GraphvizOutput(output_file=filename)
with PyCallGraph(drawer):
<code_to_be_profiled>

NumPy

Array manipulation mini language. Can run up to one hundred times faster than equivalent Python code.

1
2
# $ pip3 install numpy
import numpy as np
1
2
3
4
<array> = np.array(<list>)
<array> = np.arange(from_inclusive, to_exclusive, ±step_size)
<array> = np.ones(<shape>)
<array> = np.random.randint(from_inclusive, to_exclusive, <shape>)
1
2
3
<array>.shape = <shape>
<view> = <array>.reshape(<shape>)
<view> = np.broadcast_to(<array>, <shape>)
1
2
<array> = <array>.sum(axis)
indexes = <array>.argmin(axis)
  • Shape is a tuple of dimension sizes.
  • Axis is an index of dimension that gets collapsed. Leftmost dimension has index 0.

Indexing

1
2
3
4
<el>       = <2d_array>[0, 0]        # First element.
<1d_view> = <2d_array>[0] # First row.
<1d_view> = <2d_array>[:, 0] # First column. Also [..., 0].
<3d_view> = <2d_array>[None, :, :] # Expanded by dimension of size 1.
1
2
<1d_array> = <2d_array>[<1d_row_indexes>, <1d_column_indexes>]
<2d_array> = <2d_array>[<2d_row_indexes>, <2d_column_indexes>]
1
2
<2d_bools> = <2d_array> > 0
<1d_array> = <2d_array>[<2d_bools>]
  • If row and column indexes differ in shape, they are combined with broadcasting.

Broadcasting

Broadcasting is a set of rules by which NumPy functions operate on arrays of different sizes and/or dimensions.

1
2
left  = [[0.1], [0.6], [0.8]]        # Shape: (3, 1)
right = [ 0.1 , 0.6 , 0.8 ] # Shape: (3)

1. If array shapes differ in length, left-pad the shorter shape with ones:

1
2
left  = [[0.1], [0.6], [0.8]]        # Shape: (3, 1)
right = [[0.1 , 0.6 , 0.8]] # Shape: (1, 3) <- !

2. If any dimensions differ in size, expand the ones that have size 1 by duplicating their elements:

1
2
left  = [[0.1, 0.1, 0.1], [0.6, 0.6, 0.6], [0.8, 0.8, 0.8]]  # Shape: (3, 3) <- !
right = [[0.1, 0.6, 0.8], [0.1, 0.6, 0.8], [0.1, 0.6, 0.8]] # Shape: (3, 3) <- !

3. If neither non-matching dimension has size 1, rise an error.

Example

For each point returns index of its nearest point ([0.1, 0.6, 0.8] => [1, 2, 1]):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
>>> points = np.array([0.1, 0.6, 0.8])
[ 0.1, 0.6, 0.8]
>>> wrapped_points = points.reshape(3, 1)
[[ 0.1],
[ 0.6],
[ 0.8]]
>>> distances = wrapped_points - points
[[ 0. , -0.5, -0.7],
[ 0.5, 0. , -0.2],
[ 0.7, 0.2, 0. ]]
>>> distances = np.abs(distances)
[[ 0. , 0.5, 0.7],
[ 0.5, 0. , 0.2],
[ 0.7, 0.2, 0. ]]
>>> i = np.arange(3)
[0, 1, 2]
>>> distances[i, i] = np.inf
[[ inf, 0.5, 0.7],
[ 0.5, inf, 0.2],
[ 0.7, 0.2, inf]]
>>> distances.argmin(1)
[1, 2, 1]

Image

1
2
# $ pip3 install pillow
from PIL import Image
1
2
3
4
5
<Image> = Image.new('<mode>', (width, height))
<Image> = Image.open('<path>')
<Image> = <Image>.convert('<mode>')
<Image>.save('<path>')
<Image>.show()
1
2
3
4
5
<tuple/int> = <Image>.getpixel((x, y))          # Returns a pixel.
<Image>.putpixel((x, y), <tuple/int>) # Writes a pixel to image.
<ImagingCore> = <Image>.getdata() # Returns a sequence of pixels.
<Image>.putdata(<list/ImagingCore>) # Writes a sequence of pixels.
<Image>.paste(<Image>, (x, y)) # Writes an image to image.
1
2
3
<2d_array> = np.array(<Image>)                  # NumPy array from greyscale image.
<3d_array> = np.array(<Image>) # NumPy array from color image.
<Image> = Image.fromarray(<array>) # Image from NumPy array.

Modes

  • '1' - 1-bit pixels, black and white, stored with one pixel per byte.
  • 'L' - 8-bit pixels, greyscale.
  • 'RGB' - 3x8-bit pixels, true color.
  • 'RGBA' - 4x8-bit pixels, true color with transparency mask.
  • 'HSV' - 3x8-bit pixels, Hue, Saturation, Value color space.

Examples

Creates a PNG image of a rainbow gradient:

1
2
3
4
5
6
WIDTH, HEIGHT = 100, 100
size = WIDTH * HEIGHT
hues = [255 * i/size for i in range(size)]
img = Image.new('HSV', (WIDTH, HEIGHT))
img.putdata([(int(h), 255, 255) for h in hues])
img.convert('RGB').save('test.png')

Adds noise to a PNG image:

1
2
3
4
5
from random import randint
add_noise = lambda value: max(0, min(255, value + randint(-20, 20)))
img = Image.open('test.png').convert('HSV')
img.putdata([(add_noise(h), s, v) for h, s, v in img.getdata()])
img.convert('RGB').save('test.png')

Drawing

1
from PIL import ImageDraw
1
2
3
4
5
6
7
<ImageDraw> = ImageDraw.Draw(<Image>)
<ImageDraw>.point((x, y), fill=None)
<ImageDraw>.line((x1, y1, x2, y2 [, ...]), fill=None, width=0, joint=None)
<ImageDraw>.arc((x1, y1, x2, y2), from_deg, to_deg, fill=None, width=0)
<ImageDraw>.rectangle((x1, y1, x2, y2), fill=None, outline=None, width=0)
<ImageDraw>.polygon((x1, y1, x2, y2 [, ...]), fill=None, outline=None)
<ImageDraw>.ellipse((x1, y1, x2, y2), fill=None, outline=None, width=0)
  • Use 'fill=<color>' to set the primary color.
  • Use 'outline=<color>' to set the secondary color.
  • Color can be specified as a tuple, int, '#rrggbb' string or a color name.

Animation

Creates a GIF of a bouncing ball:

1
2
3
4
5
6
7
8
9
10
11
12
13
# $ pip3 install pillow imageio
from PIL import Image, ImageDraw
import imageio
WIDTH, R = 126, 10
frames = []
for velocity in range(15):
y = sum(range(velocity+1))
frame = Image.new('L', (WIDTH, WIDTH))
draw = ImageDraw.Draw(frame)
draw.ellipse((WIDTH/2-R, y, WIDTH/2+R, y+2*R), fill='white')
frames.append(frame)
frames += reversed(frames[1:-1])
imageio.mimsave('test.gif', frames, duration=0.03)

Audio

1
import wave
1
2
3
4
5
6
7
<Wave_read>  = wave.open('<path>', 'rb')
framerate = <Wave_read>.getframerate() # Number of frames per second.
nchannels = <Wave_read>.getnchannels() # Number of samples per frame.
sampwidth = <Wave_read>.getsampwidth() # Sample size in bytes.
nframes = <Wave_read>.getnframes() # Number of frames.
<params> = <Wave_read>.getparams() # Immutable collection of above.
<bytes> = <Wave_read>.readframes(nframes) # Returns next 'nframes' frames.
1
2
3
4
5
6
<Wave_write> = wave.open('<path>', 'wb')
<Wave_write>.setframerate(<int>) # 44100 for CD, 48000 for video.
<Wave_write>.setnchannels(<int>) # 1 for mono, 2 for stereo.
<Wave_write>.setsampwidth(<int>) # 2 for CD quality sound.
<Wave_write>.setparams(<params>) # Sets all parameters.
<Wave_write>.writeframes(<bytes>) # Appends frames to file.
  • Bytes object contains a sequence of frames, each consisting of one or more samples.
  • In stereo signal first sample of a frame belongs to the left channel.
  • Each sample consists of one or more bytes that, when converted to an integer, indicate the displacement of a speaker membrane at a given moment.
  • If sample width is one, then the integer should be encoded unsigned.
  • For all other sizes the integer should be encoded signed with little-endian byte order.

Sample Values

1
2
3
4
5
6
7
8
+-----------+-------------+------+-------------+
| sampwidth | min | zero | max |
+-----------+-------------+------+-------------+
| 1 | 0 | 128 | 255 |
| 2 | -32768 | 0 | 32767 |
| 3 | -8388608 | 0 | 8388607 |
| 4 | -2147483648 | 0 | 2147483647 |
+-----------+-------------+------+-------------+

Read Float Samples from WAV File

1
2
3
4
5
6
7
8
9
def read_wav_file(filename):
def get_int(a_bytes):
an_int = int.from_bytes(a_bytes, 'little', signed=width!=1)
return an_int - 128 * (width == 1)
with wave.open(filename, 'rb') as file:
width = file.getsampwidth()
frames = file.readframes(file.getnframes())
byte_samples = (frames[i: i + width] for i in range(0, len(frames), width))
return [get_int(b) / pow(2, width * 8 - 1) for b in byte_samples]

Write Float Samples to WAV File

1
2
3
4
5
6
7
8
9
10
11
def write_to_wav_file(filename, float_samples, nchannels=1, sampwidth=2, framerate=44100):
def get_bytes(a_float):
a_float = max(-1, min(1 - 2e-16, a_float))
a_float += sampwidth == 1
a_float *= pow(2, sampwidth * 8 - 1)
return int(a_float).to_bytes(sampwidth, 'little', signed=sampwidth!=1)
with wave.open(filename, 'wb') as file:
file.setnchannels(nchannels)
file.setsampwidth(sampwidth)
file.setframerate(framerate)
file.writeframes(b''.join(get_bytes(f) for f in float_samples))

Examples

Saves a sine wave to a mono WAV file:

1
2
3
from math import pi, sin
samples_f = (sin(i * 2 * pi * 440 / 44100) for i in range(100000))
write_to_wav_file('test.wav', samples_f)

Adds noise to a mono WAV file:

1
2
3
4
from random import random
add_noise = lambda value: value + (random() - 0.5) * 0.03
samples_f = (add_noise(f) for f in read_wav_file('test.wav'))
write_to_wav_file('test.wav', samples_f)

Plays a WAV file:

1
2
3
4
5
6
# $ pip3 install simpleaudio
from simpleaudio import play_buffer
with wave.open('test.wav', 'rb') as file:
p = file.getparams()
frames = file.readframes(p.nframes)
play_buffer(frames, p.nchannels, p.sampwidth, p.framerate)

Text to Speech

1
2
3
4
5
# $ pip3 install pyttsx3
import pyttsx3
engine = pyttsx3.init()
engine.say('Sally sells seashells by the seashore.')
engine.runAndWait()

Synthesizer

Plays Popcorn by Gershon Kingsley:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# $ pip3 install simpleaudio
import simpleaudio, math, struct
from itertools import chain, repeat
F = 44100
P1 = '71♪,69,,71♪,66,,62♪,66,,59♪,,,'
P2 = '71♪,73,,74♪,73,,74,,71,,73♪,71,,73,,69,,71♪,69,,71,,67,,71♪,,,'
get_pause = lambda seconds: repeat(0, int(seconds * F))
sin_f = lambda i, hz: math.sin(i * 2 * math.pi * hz / F)
get_wave = lambda hz, seconds: (sin_f(i, hz) for i in range(int(seconds * F)))
get_hz = lambda key: 8.176 * 2 ** (int(key) / 12)
parse_note = lambda note: (get_hz(note[:2]), 0.25 if '♪' in note else 0.125)
get_samples = lambda note: get_wave(*parse_note(note)) if note else get_pause(0.125)
samples_f = chain.from_iterable(get_samples(n) for n in f'{P1}{P1}{P2}'.split(','))
samples_b = b''.join(struct.pack('<h', int(f * 30000)) for f in samples_f)
simpleaudio.play_buffer(samples_b, 1, 2, F)

Basic Script Template

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#!/usr/bin/env python3
#
# Usage: .py
#

from collections import namedtuple
from dataclasses import make_dataclass
from enum import Enum
from sys import argv
import re


def main():
pass


###
## UTIL
#

def read_file(filename):
with open(filename, encoding='utf-8') as file:
return file.readlines()


if __name__ == '__main__':
main()

在提问之前

在你准备要通过电子邮件、新闻群组或者聊天室提出技术问题前,请先做到以下事情:

1. 尝试在你准备提问的论坛的旧文章中搜索答案。
2. 尝试上网搜索以找到答案。
3. 尝试阅读手册以找到答案。
4. 尝试阅读常见问题文件(FAQ)以找到答案。
5. 尝试自己检查或试验以找到答案。
6. 向你身边的强者朋友打听以找到答案。
7. 如果你是程序开发者,请尝试阅读源代码以找到答案。

当你提出问题的时候,请先表明你已经做了上述的努力;这将有助于树立你并不是一个不劳而获且浪费别人的时间的提问者。如果你能一并表达在做了上述努力的过程中所学到的东西会更好,因为我们更乐于回答那些表现出能从答案中学习的人的问题。

运用某些策略,比如先用 Google 搜索你所遇到的各种错误信息(既搜索 Google 论坛,也搜索网页),这样很可能直接就找到了能解决问题的文件或邮件列表线索。即使没有结果,在邮件列表或新闻组寻求帮助时加上一句 我在 Google 中搜过下列句子但没有找到什么有用的东西 也是件好事,即使它只是表明了搜索引擎不能提供哪些帮助。这么做(加上搜索过的字串)也让遇到相似问题的其他人能被搜索引擎引导到你的提问来。

别着急,不要指望几秒钟的 Google 搜索就能解决一个复杂的问题。在向专家求助之前,再阅读一下常见问题文件(FAQ)、放轻松、坐舒服一些,再花点时间思考一下这个问题。相信我们,他们能从你的提问看出你做了多少阅读与思考,如果你是有备而来,将更有可能得到解答。不要将所有问题一股脑拋出,只因你的第一次搜索没有找到答案(或者找到太多答案)。

准备好你的问题,再将问题仔细的思考过一遍,因为草率的发问只能得到草率的回答,或者根本得不到任何答案。越是能表现出在寻求帮助前你为解决问题所付出的努力,你越有可能得到实质性的帮助。

小心别问错了问题。如果你的问题基于错误的假设,某个普通黑客(J. Random Hacker)多半会一边在心里想着蠢问题…, 一边用无意义的字面解释来答复你,希望着你会从问题的回答(而非你想得到的答案)中汲取教训。

绝不要自以为够格得到答案,你没有;你并没有。毕竟你没有为这种服务支付任何报酬。你将会是自己去挣到一个答案,靠提出有内涵的、有趣的、有思维激励作用的问题 —— 一个有潜力能贡献社区经验的问题,而不仅仅是被动的从他人处索取知识。

另一方面,表明你愿意在找答案的过程中做点什么是一个非常好的开端。谁能给点提示?我的这个例子里缺了什么?以及我应该检查什么地方请把我需要的确切的过程贴出来更容易得到答复。因为你表现出只要有人能指个正确方向,你就有完成它的能力和决心。

当你提问时

慎选提问的论坛

小心选择你要提问的场合。如果你做了下述的事情,你很可能被忽略掉或者被看作失败者:

  • 在与主题不合的论坛上贴出你的问题。
  • 在探讨进阶技术问题的论坛张贴非常初级的问题;反之亦然。
  • 在太多的不同新闻群组上重复转贴同样的问题(cross-post)。
  • 向既非熟人也没有义务解决你问题的人发送私人电邮。

黑客会剔除掉那些搞错场合的问题,以保护他们沟通的渠道不被无关的东西淹没。你不会想让这种事发生在自己身上的。

因此,第一步是找到对的论坛。再说一次,Google 和其它搜索引擎还是你的朋友,用它们来找到与你遭遇到困难的软硬件问题最相关的网站。通常那儿都有常见问题(FAQ)、邮件列表及相关说明文件的链接。如果你的努力(包括阅读 FAQ)都没有结果,网站上也许还有报告 Bug(Bug-reporting)的流程或链接,如果是这样,链过去看看。

向陌生的人或论坛发送邮件最可能是风险最大的事情。举例来说,别假设一个提供丰富内容的网页的作者会想充当你的免费顾问。不要对你的问题是否会受到欢迎做太乐观的估计 – 如果你不确定,那就向别处发送,或者压根别发。

在选择论坛、新闻群组或邮件列表时,别太相信名字,先看看 FAQ 或者许可书以弄清楚你的问题是否切题。发文前先翻翻已有的话题,这样可以让你感受一下那里的文化。事实上,事先在新闻组或邮件列表的历史记录中搜索与你问题相关的关键词是个极好的主意,也许这样就找到答案了。即使没有,也能帮助你归纳出更好的问题。

别像机关枪似的一次”扫射”所有的帮助渠道,这就像大喊大叫一样会使人不快。要一个一个地来。

搞清楚你的主题!最典型的错误之一是在某种致力于跨平台可移植的语言、套件或工具的论坛中提关于 Unix 或 Windows 操作系统程序界面的问题。如果你不明白为什么这是大错,最好在搞清楚这之间差异之前什么也别问。

一般来说,在仔细挑选的公共论坛中提问,会比在私有论坛中提同样的问题更容易得到有用的回答。有几个理由可以支持这点,一是看潜在的回复者有多少,二是看观众有多少。黑客较愿意回答那些能帮助到许多人的问题。

可以理解的是,老练的黑客和一些热门软件的作者正在接受过多的错发信息。就像那根最后压垮骆驼背的稻草一样,你的加入也有可能使情况走向极端 —— 已经好几次了,一些热门软件的作者从自己软件的支持中抽身出来,因为伴随而来涌入其私人邮箱的无用邮件变得无法忍受。

Stack Overflow

搜索,然后 在 Stack Exchange 问。

近年来,Stack Exchange community 社区已经成为回答技术及其他问题的主要渠道,尤其是那些开放源码的项目。

因为 Google 索引是即时的,在看 Stack Exchange 之前先在 Google 搜索。有很高的机率某人已经问了一个类似的问题,而且 Stack Exchange 网站们往往会是搜索结果中最前面几个。如果你在 Google 上没有找到任何答案,你再到特定相关主题的网站去找。用标签(Tag)搜索能让你更缩小你的搜索结果。

Stack Exchange 已经成长到超过一百个网站,以下是最常用的几个站:

  • Super User 是问一些通用的电脑问题,如果你的问题跟代码或是写程序无关,只是一些网络连线之类的,请到这里。
  • Stack Overflow 是问写程序有关的问题。
  • Server Fault 是问服务器和网管相关的问题。

网站和 IRC 论坛

本地的使用者群组(user group),或者你所用的 Linux 发行版本也许正在宣传他们的网页论坛或 IRC 频道,并提供新手帮助(在一些非英语国家,新手论坛很可能还是邮件列表), 这些地方是开始提问的好首选,特别是当你觉得遇到的也许只是相对简单或者很普通的问题时。有广告赞助的 IRC 频道是公开欢迎提问的地方,通常可以即时得到回应。

事实上,如果程序出的问题只发生在特定 Linux 发行版提供的版本(这很常见),最好先去该发行版的论坛或邮件列表中提问,再到程序本身的论坛或邮件列表提问。(否则)该项目的黑客可能仅仅回复 “用我们的版本”。

在任何论坛发文以前,先确认一下有没有搜索功能。如果有,就试着搜索一下问题的几个关键词,也许这会有帮助。如果在此之前你已做过通用的网页搜索(你也该这样做),还是再搜索一下论坛,搜索引擎有可能没来得及索引此论坛的全部内容。

通过论坛或 IRC 频道来提供使用者支持服务有增长的趋势,电子邮件则大多为项目开发者间的交流而保留。所以最好先在论坛或 IRC 中寻求与该项目相关的协助。

在使用 IRC 的时候,首先最好不要发布很长的问题描述,有些人称之为频道洪水。最好通过一句话的问题描述来开始聊天。

第二步,使用项目邮件列表

当某个项目提供开发者邮件列表时,要向列表而不是其中的个别成员提问,即使你确信他能最好地回答你的问题。查一查项目的文件和首页,找到项目的邮件列表并使用它。有几个很好的理由支持我们采用这种办法:

  • 任何好到需要向个别开发者提出的问题,也将对整个项目群组有益。反之,如果你认为自己的问题对整个项目群组来说太愚蠢,也不能成为骚扰个别开发者的理由。
  • 向列表提问可以分散开发者的负担,个别开发者(尤其是项目领导人)也许太忙以至于没法回答你的问题。
  • 大多数邮件列表都会被存档,那些被存档的内容将被搜索引擎索引。如果你向列表提问并得到解答,将来其它人可以通过网页搜索找到你的问题和答案,也就不用再次发问了。
  • 如果某些问题经常被问到,开发者可以利用此信息来改进说明文件或软件本身,以使其更清楚。如果只是私下提问,就没有人能看到最常见问题的完整场景。

如果一个项目既有”使用者” 也有”开发者”(或”黑客”)邮件列表或论坛,而你又不会动到那些源代码,那么就向”使用者”列表或论坛提问。不要假设自己会在开发者列表中受到欢迎,那些人多半会将你的提问视为干扰他们开发的噪音。

然而,如果你确信你的问题很特别,而且在”使用者” 列表或论坛中几天都没有回复,可以试试前往”开发者”列表或论坛发问。建议你在张贴前最好先暗地里观察几天以了解那里的行事方式(事实上这是参与任何私有或半私有列表的好主意)

如果你找不到一个项目的邮件列表,而只能查到项目维护者的电子邮件地址,尽管向他发信。即使是在这种情况下,也别假设(项目)邮件列表不存在。在你的电子邮件中,请陈述你已经试过但没有找到合适的邮件列表,也提及你不反对将自己的邮件转发给他人(许多人认为,即使没什么秘密,私人电子邮件也不应该被公开。通过允许将你的电子邮件转发他人,你给了相应人员处置你邮件的选择)。

使用有意义且描述明确的标题

在邮件列表、新闻群组或论坛中,大约 50 字以内的标题是抓住资深专家注意力的好机会。别用喋喋不休的帮帮忙跪求(更别说救命啊!!!!这样让人反感的话,用这种标题会被条件反射式地忽略)来浪费这个机会。不要妄想用你的痛苦程度来打动我们,而应该是在这点空间中使用极简单扼要的描述方式来提出问题。

一个好标题范例是目标 —— 差异式的描述,许多技术支持组织就是这样做的。在目标部分指出是哪一个或哪一组东西有问题,在差异部分则描述与期望的行为不一致的地方。

蠢问题:救命啊!我的笔记本电脑不能正常显示了!

聪明问题:X.org 6.8.1 的鼠标光标会变形,某牌显卡 MV1005 芯片组。

更聪明问题:X.org 6.8.1 的鼠标光标,在某牌显卡 MV1005 芯片组环境下 - 会变形。

编写目标 —— 差异 式描述的过程有助于你组织对问题的细致思考。是什么被影响了? 仅仅是鼠标光标或者还有其它图形?只在 X.org 的 X 版中出现?或只是出现在 6.8.1 版中? 是针对某牌显卡芯片组?或者只是其中的 MV1005 型号? 一个黑客只需瞄一眼就能够立即明白你的环境你遇到的问题。

总而言之,请想像一下你正在一个只显示标题的存档讨论串(Thread)索引中查寻。让你的标题更好地反映问题,可使下一个搜索类似问题的人能够关注这个讨论串,而不用再次提问相同的问题。

如果你想在回复中提出问题,记得要修改内容标题,以表明你是在问一个问题, 一个看起来像 Re: 测试 或者 Re: 新 bug 的标题很难引起足够重视。另外,在不影响连贯性之下,适当引用并删减前文的内容,能给新来的读者留下线索。

对于讨论串,不要直接点击回复来开始一个全新的讨论串,这将限制你的观众。因为有些邮件阅读程序,比如 mutt ,允许使用者按讨论串排序并通过折叠讨论串来隐藏消息,这样做的人永远看不到你发的消息。

仅仅改变标题还不够。mutt 和其它一些邮件阅读程序还会检查邮件标题以外的其它信息,以便为其指定讨论串。所以宁可发一个全新的邮件。

在网页论坛上,好的提问方式稍有不同,因为讨论串与特定的信息紧密结合,并且通常在讨论串外就看不到里面的内容,故通过回复提问,而非改变标题是可接受的。不是所有论坛都允许在回复中出现分离的标题,而且这样做了基本上没有人会去看。不过,通过回复提问,这本身就是暧昧的做法,因为它们只会被正在查看该标题的人读到。所以,除非你只想在该讨论串当前活跃的人群中提问,不然还是另起炉灶比较好。

使问题容易回复

请将你的回复发送到……来结束你的问题多半会使你得不到回答。如果你觉得花几秒钟在邮件客户端设置一下回复地址都麻烦,我们也觉得花几秒钟思考你的问题更麻烦。如果你的邮件程序不支持这样做,换个好点的;如果是操作系统不支持这种邮件程序,也换个好点的。

在论坛,要求通过电子邮件回复是非常无礼的,除非你认为回复的信息可能比较敏感(有人会为了某些未知的原因,只让你而不是整个论坛知道答案)。如果你只是想在有人回复讨论串时得到电子邮件提醒,可以要求网页论坛发送给你。几乎所有论坛都支持诸如追踪此讨论串有回复时发送邮件提醒等功能。

用清晰、正确、精准并语法正确的语句

我们从经验中发现,粗心的提问者通常也会粗心的写程序与思考(我敢打包票)。回答粗心大意者的问题很不值得,我们宁愿把时间耗在别处。

正确的拼写、标点符号和大小写是很重要的。一般来说,如果你觉得这样做很麻烦,不想在乎这些,那我们也觉得麻烦,不想在乎你的提问。花点额外的精力斟酌一下字句,用不着太僵硬与正式 —— 事实上,黑客文化很看重能准确地使用非正式、俚语和幽默的语句。但它必须很准确,而且有迹象表明你是在思考和关注问题。

正确地拼写、使用标点和大小写,不要将its混淆为it'sloose搞成lose或者将discrete弄成discreet。不要全部用大写,这会被视为无礼的大声嚷嚷(全部小写也好不到哪去,因为不易阅读。Alan Cox 也许可以这样做,但你不行)。

更白话的说,如果你写得像是个半文盲[译注:小白],那多半得不到理睬。也不要使用即时通信中的简写或火星文,如将简化为d会使你看起来像一个为了少打几个键而省字的小白。更糟的是,如果像个小孩似地鬼画符那绝对是在找死,可以肯定没人会理你(或者最多是给你一大堆指责与挖苦)。

如果在使用非母语的论坛提问,你可以犯点拼写和语法上的小错,但决不能在思考上马虎(没错,我们通常能弄清两者的分别)。同时,除非你知道回复者使用的语言,否则请使用英语书写。繁忙的黑客一般会直接删除用他们看不懂语言写的消息。在网络上英语是通用语言,用英语书写可以将你的问题在尚未被阅读就被直接删除的可能性降到最低。

如果英文是你的外语(Second language),提示潜在回复者你有潜在的语言困难是很好的:
[译注:以下附上原文以供使用]

English is not my native language; please excuse typing errors.

  • 英文不是我的母语,请原谅我的错字或语法。

If you speak $LANGUAGE, please email/PM me;
I may need assistance translating my question.

  • 如果你说某语言,请寄信/私讯给我;我需要有人协助我翻译我的问题。

I am familiar with the technical terms,
but some slang expressions and idioms are difficult for me.

  • 我对技术名词很熟悉,但对于俗语或是特别用法比较不甚了解。

I’ve posted my question in $LANGUAGE and English.
I’ll be glad to translate responses, if you only use one or the other.

  • 我把我的问题用某语言和英文写出来,如果你只用一种语言回答,我会乐意将其翻译成另一种。

使用易于读取且标准的文件格式发送问题

如果你人为地将问题搞得难以阅读,它多半会被忽略,人们更愿读易懂的问题,所以:

  • 使用纯文字而不是 HTML (关闭 HTML 并不难)。
  • 使用 MIME 附件通常是可以的,前提是真正有内容(譬如附带的源代码或 patch),而不仅仅是邮件程序生成的模板(譬如只是信件内容的拷贝)。
  • 不要发送一段文字只是一行句子但自动换行后会变成多行的邮件(这使得回复部分内容非常困难)。设想你的读者是在 80 个字符宽的终端机上阅读邮件,最好设置你的换行分割点小于 80 字。
  • 但是,对一些特殊的文件不要设置固定宽度(譬如日志档案拷贝或会话记录)。数据应该原样包含,让回复者有信心他们看到的是和你看到的一样的东西。
  • 在英语论坛中,不要使用Quoted-Printable MIME 编码发送消息。这种编码对于张贴非 ASCII 语言可能是必须的,但很多邮件程序并不支持这种编码。当它们处理换行时,那些文本中四处散布的=20符号既难看也分散注意力,甚至有可能破坏内容的语意。
  • 绝对,永远不要指望黑客们阅读使用封闭格式编写的文档,像微软公司的 Word 或 Excel 文件等。大多数黑客对此的反应就像有人将还在冒热气的猪粪倒在你家门口时你的反应一样。即便他们能够处理,他们也很厌恶这么做。
  • 如果你从使用 Windows 的电脑发送电子邮件,关闭微软愚蠢的智能引号功能 (从[选项] > [校订] > [自动校正选项],勾选掉智能引号单选框),以免在你的邮件中到处散布垃圾字符。
  • 在论坛,勿滥用表情符号HTML功能(当它们提供时)。一两个表情符号通常没有问题,但花哨的彩色文本倾向于使人认为你是个无能之辈。过滥地使用表情符号、色彩和字体会使你看来像个傻笑的小姑娘。这通常不是个好主意,除非你只是对性而不是对答案感兴趣。

如果你使用图形用户界面的邮件程序(如微软公司的 Outlook 或者其它类似的),注意它们的默认设置不一定满足这些要求。大多数这类程序有基于选单的查看源代码命令,用它来检查发送文件夹中的邮件,以确保发送的是纯文本文件同时没有一些奇怪的字符。

精确地描述问题并言之有物

  • 仔细、清楚地描述你的问题或 Bug 的症状。
  • 描述问题发生的环境(机器配置、操作系统、应用程序、以及相关的信息),提供经销商的发行版和版本号(如:Fedora Core 4Slackware 9.1等)。
  • 描述在提问前你是怎样去研究和理解这个问题的。
  • 描述在提问前为确定问题而采取的诊断步骤。
  • 描述最近做过什么可能相关的硬件或软件变更。
  • 尽可能的提供一个可以重现这个问题的可控环境的方法。

尽量去揣测一个黑客会怎样反问你,在你提问之前预先将黑客们可能遇到的问题回答一遍。

以上几点中,当你报告的是你认为可能在代码中的问题时,给黑客一个可以重现你的问题的环境尤其重要。当你这么做时,你得到有效的回答的机会和速度都会大大的提升。

Simon Tatham 写过一篇名为《如何有效的报告 Bug》的出色文章。强力推荐你也读一读。

话不在多而在精

你需要提供精确有内容的信息。这并不是要求你简单的把成堆的出错代码或者资料完全转录到你的提问中。如果你有庞大而复杂的测试样例能重现程序挂掉的情境,尽量将它剪裁得越小越好。

这样做的用处至少有三点。
第一,表现出你为简化问题付出了努力,这可以使你得到回答的机会增加;
第二,简化问题使你更有可能得到有用的答案;
第三,在精炼你的 bug 报告的过程中,你很可能就自己找到了解决方法或权宜之计。

别动辄声称找到 Bug

当你在使用软件中遇到问题,除非你非常、非常的有根据,不要动辄声称找到了 Bug。提示:除非你能提供解决问题的源代码补丁,或者提供回归测试来表明前一版本中行为不正确,否则你都多半不够完全确信。这同样适用在网页和文件,如果你(声称)发现了文件的Bug,你应该能提供相应位置的修正或替代文件。

请记得,还有许多其它使用者没遇到你发现的问题,否则你在阅读文件或搜索网页时就应该发现了(你在抱怨前已经做了这些,是吧?)。这也意味着很有可能是你弄错了而不是软件本身有问题。

编写软件的人总是非常辛苦地使它尽可能完美。如果你声称找到了 Bug,也就是在质疑他们的能力,即使你是对的,也有可能会冒犯到其中某部分人。当你在标题中嚷嚷着有Bug时,这尤其严重。

提问时,即使你私下非常确信已经发现一个真正的 Bug,最好写得像是做错了什么。如果真的有 Bug,你会在回复中看到这点。这样做的话,如果真有 Bug,维护者就会向你道歉,这总比你惹恼别人然后欠别人一个道歉要好一点。

低声下气不能代替你的功课

有些人明白他们不该粗鲁或傲慢的提问并要求得到答复,但他们选择另一个极端 —— 低声下气:我知道我只是个可悲的新手,一个撸瑟,但...。这既使人困扰,也没有用,尤其是伴随着与实际问题含糊不清的描述时更令人反感。

别用原始灵长类动物的把戏来浪费你我的时间。取而代之的是,尽可能清楚地描述背景条件和你的问题情况。这比低声下气更好地定位了你的位置。

有时网页论坛会设有专为新手提问的版面,如果你真的认为遇到了初学者的问题,到那去就是了,但一样别那么低声下气。

描述问题症状而非你的猜测

告诉黑客们你认为问题是怎样造成的并没什么帮助。(如果你的推断如此有效,还用向别人求助吗?),因此要确信你原原本本告诉了他们问题的症状,而不是你的解释和理论;让黑客们来推测和诊断。如果你认为陈述自己的猜测很重要,清楚地说明这只是你的猜测,并描述为什么它们不起作用。

蠢问题

我在编译内核时接连遇到 SIG11 错误,
我怀疑某条飞线搭在主板的走线上了,这种情况应该怎样检查最好?

聪明问题

我的组装电脑是 FIC-PA2007 主机板搭载 AMD K6/233 CPU(威盛 Apollo VP2 芯片组),
256MB Corsair PC133 SDRAM 内存,在编译内核时,从开机 20 分钟以后就频频产生 SIG11 错误,
但是在头 20 分钟内从没发生过相同的问题。重新启动也没有用,但是关机一晚上就又能工作 20 分钟。
所有内存都换过了,没有效果。相关部分的标准编译记录如下…。

由于以上这点似乎让许多人觉得难以配合,这里有句话可以提醒你:所有的诊断专家都来自密苏里州。 美国国务院的官方座右铭则是:让我看看(出自国会议员 Willard D. Vandiver 在 1899 年时的讲话:我来自一个出产玉米,棉花,牛蒡和民主党人的国家,滔滔雄辩既不能说服我,也不会让我满意。我来自密苏里州,你必须让我看看。) 针对诊断者而言,这并不是一种怀疑,而只是一种真实而有用的需求,以便让他们看到的是与你看到的原始证据尽可能一致的东西,而不是你的猜测与归纳的结论。所以,大方的展示给我们看吧!

按发生时间先后列出问题症状

问题发生前的一系列操作,往往就是对找出问题最有帮助的线索。因此,你的说明里应该包含你的操作步骤,以及机器和软件的反应,直到问题发生。在命令行处理的情况下,提供一段操作记录(例如运行脚本工具所生成的),并引用相关的若干行(如 20 行)记录会非常有帮助。

如果挂掉的程序有诊断选项(如 -v 的详述开关),试着选择这些能在记录中增加调试信息的选项。记住,不等于。试着选取适当的调试级别以便提供有用的信息而不是让读者淹没在垃圾中。

如果你的说明很长(如超过四个段落),在开头简述问题,接下来再按时间顺序详述会有所帮助。这样黑客们在读你的记录时就知道该注意哪些内容了。

描述目标而不是过程

如果你想弄清楚如何做某事(而不是报告一个 Bug),在开头就描述你的目标,然后才陈述重现你所卡住的特定步骤。

经常寻求技术帮助的人在心中有个更高层次的目标,而他们在自以为能达到目标的特定道路上被卡住了,然后跑来问该怎么走,但没有意识到这条路本身就有问题。结果要费很大的劲才能搞定。

蠢问题

我怎样才能从某绘图程序的颜色选择器中取得十六进制的的 RGB 值?

聪明问题

我正试着用替换一幅图片的色码(color table)成自己选定的色码,我现在知道的唯一方法是编辑每个色码区块(table slot),
但却无法从某绘图程序的颜色选择器取得十六进制的的 RGB 值。

第二种提问法比较聪明,你可能得到像是建议采用另一个更合适的工具的回复。

别要求使用私人电邮回复

黑客们认为问题的解决过程应该公开、透明,此过程中如果更有经验的人注意到不完整或者不当之处,最初的回复才能够、也应该被纠正。同时,作为提供帮助者可以得到一些奖励,奖励就是他的能力和学识被其他同行看到。

当你要求私下回复时,这个过程和奖励都被中止。别这样做,让回复者来决定是否私下回答 —— 如果他真这么做了,通常是因为他认为问题编写太差或者太肤浅,以至于对其它人没有兴趣。

这条规则存在一条有限的例外,如果你确信提问可能会引来大量雷同的回复时,那么这个神奇的提问句会是向我发电邮,我将为论坛归纳这些回复。试着将邮件列表或新闻群组从洪水般的雷同回复中解救出来是非常有礼貌的 —— 但你必须信守诺言。

清楚明确的表达你的问题以及需求

漫无边际的提问是近乎无休无止的时间黑洞。最有可能给你有用答案的人通常也正是最忙的人(他们忙是因为要亲自完成大部分工作)。这样的人对无节制的时间黑洞相当厌恶,所以他们也倾向于厌恶那些漫无边际的提问。

如果你明确表述需要回答者做什么(如提供指点、发送一段代码、检查你的补丁、或是其他等等),就最有可能得到有用的答案。因为这会定出一个时间和精力的上限,便于回答者能集中精力来帮你。这么做很棒。

要理解专家们所处的世界,请把专业技能想像为充裕的资源,而回复的时间则是稀缺的资源。你要求他们奉献的时间越少,你越有可能从真正专业而且很忙的专家那里得到解答。

所以,界定一下你的问题,使专家花在辨识你的问题和回答所需要付出的时间减到最少,这技巧对你有用答案相当有帮助 —— 但这技巧通常和简化问题有所区别。因此,问我想更好的理解 X,可否指点一下哪有好一点说明?通常比问你能解释一下 X 吗?更好。如果你的代码不能运作,通常请别人看看哪里有问题,比要求别人替你改正要明智得多。

询问有关代码的问题时

别要求他人帮你调试有问题的代码,不提示一下应该从何入手。张贴几百行的代码,然后说一声:它不能工作会让你完全被忽略。只贴几十行代码,然后说一句:在第七行以后,我期待它显示 <x>,但实际出现的是 <y>比较有可能让你得到回应。

最有效描述程序问题的方法是提供最精简的 Bug 展示测试用例(bug-demonstrating test case)。什么是最精简的测试用例?那是问题的缩影;一小个程序片段能刚好展示出程序的异常行为,而不包含其他令人分散注意力的内容。怎么制作最精简的测试用例?如果你知道哪一行或哪一段代码会造成异常的行为,复制下来并加入足够重现这个状况的代码(例如,足以让这段代码能被编译/直译/被应用程序处理)。如果你无法将问题缩减到一个特定区块,就复制一份代码并移除不影响产生问题行为的部分。总之,测试用例越小越好(查看话不在多而在精一节)。

一般而言,要得到一段相当精简的测试用例并不太容易,但永远先尝试这样做的是种好习惯。这种方式可以帮助你了解如何自行解决这个问题 —— 而且即使你的尝试不成功,黑客们也会看到你在尝试取得答案的过程中付出了努力,这可以让他们更愿意与你合作。

如果你只是想让别人帮忙审查(Review)一下代码,在信的开头就要说出来,并且一定要提到你认为哪一部分特别需要关注以及为什么。

别把自己家庭作业的问题贴上来

黑客们很擅长分辨哪些问题是家庭作业式的问题;因为我们中的大多数都曾自己解决这类问题。同样,这些问题得由来搞定,你会从中学到东西。你可以要求给点提示,但别要求得到完整的解决方案。

如果你怀疑自己碰到了一个家庭作业式的问题,但仍然无法解决,试试在使用者群组,论坛或(最后一招)在项目的使用者邮件列表或论坛中提问。尽管黑客们看出来,但一些有经验的使用者也许仍会给你一些提示。

去掉无意义的提问句

避免用无意义的话结束提问,例如有人能帮我吗?或者这有答案吗?

首先:如果你对问题的描述不是很好,这样问更是画蛇添足。

其次:由于这样问是画蛇添足,黑客们会很厌烦你 —— 而且通常会用逻辑上正确,但毫无意义的回答来表示他们的蔑视, 例如:没错,有人能帮你或者不,没答案

一般来说,避免用 是或否对或错有或没有类型的问句,除非你想得到是或否类型的回答

即使你很急也不要在标题写紧急

这是你的问题,不是我们的。宣称紧急极有可能事与愿违:大多数黑客会直接删除无礼和自私地企图即时引起关注的问题。更严重的是,紧急这个字(或是其他企图引起关注的标题)通常会被垃圾信过滤器过滤掉 —— 你希望能看到你问题的人可能永远也看不到。

有半个例外的情况是,如果你是在一些很高调,会使黑客们兴奋的地方,也许值得这样去做。在这种情况下,如果你有时间压力,也很有礼貌地提到这点,人们也许会有兴趣回答快一点。

当然,这风险很大,因为黑客们兴奋的点多半与你的不同。譬如从 NASA 国际空间站(International Space Station)发这样的标题没有问题,但用自我感觉良好的慈善行为或政治原因发肯定不行。事实上,张贴诸如紧急:帮我救救这个毛绒绒的小海豹!肯定让你被黑客忽略或惹恼他们,即使他们认为毛绒绒的小海豹很重要。

如果你觉得这点很不可思议,最好再把这份指南剩下的内容多读几遍,直到你弄懂了再发文。

礼多人不怪,而且有时还很有帮助

彬彬有礼,多用谢谢您的关注,或谢谢你的关照。让大家都知道你对他们花时间免费提供帮助心存感激。

坦白说,这一点并没有比清晰、正确、精准并合法语法和避免使用专用格式重要(也不能取而代之)。黑客们一般宁可读有点唐突但技术上鲜明的 Bug 报告,而不是那种有礼但含糊的报告。(如果这点让你不解,记住我们是按问题能教给我们什么来评价问题的价值的)

然而,如果你有一串的问题待解决,客气一点肯定会增加你得到有用回应的机会。

(我们注意到,自从本指南发布后,从资深黑客那里得到的唯一严重缺陷反馈,就是对预先道谢这一条。一些黑客觉得先谢了意味着事后就不用再感谢任何人的暗示。我们的建议是要么先说先谢了然后事后再对回复者表示感谢,或者换种方式表达感激,譬如用谢谢你的关注谢谢你的关照。)

问题解决后,加个简短的补充说明

问题解决后,向所有帮助过你的人发个说明,让他们知道问题是怎样解决的,并再一次向他们表示感谢。如果问题在新闻组或者邮件列表中引起了广泛关注,应该在那里贴一个说明比较恰当。

最理想的方式是向最初提问的话题回复此消息,并在标题中包含已修正已解决或其它同等含义的明显标记。在人来人往的邮件列表里,一个看见讨论串问题 X问题 X - 已解决的潜在回复者就明白不用再浪费时间了(除非他个人觉得问题 X的有趣),因此可以利用此时间去解决其它问题。

补充说明不必很长或是很深入;简单的一句你好,原来是网线出了问题!谢谢大家 – Bill比什么也不说要来的好。事实上,除非结论真的很有技术含量,否则简短可爱的小结比长篇大论更好。说明问题是怎样解决的,但大可不必将解决问题的过程复述一遍。

对于有深度的问题,张贴调试记录的摘要是有帮助的。描述问题的最终状态,说明是什么解决了问题,在此之后才指明可以避免的盲点。避免盲点的部分应放在正确的解决方案和其它总结材料之后,而不要将此信息搞成侦探推理小说。列出那些帮助过你的名字,会让你交到更多朋友。

除了有礼貌和有内涵以外,这种类型的补充也有助于他人在邮件列表/新闻群组/论坛中搜索到真正解决你问题的方案,让他们也从中受益。

至少,这种补充有助于让每位参与协助的人因问题的解决而从中得到满足感。如果你自己不是技术专家或者黑客,那就相信我们,这种感觉对于那些你向他们求助的大师或者专家而言,是非常重要的。问题悬而未决会让人灰心;黑客们渴望看到问题被解决。好人有好报,满足他们的渴望,你会在下次提问时尝到甜头。

思考一下怎样才能避免他人将来也遇到类似的问题,自问写一份文件或加个常见问题(FAQ)会不会有帮助。如果是的话就将它们发给维护者。

在黑客中,这种良好的后继行动实际上比传统的礼节更为重要,也是你如何透过善待他人而赢得声誉的方式,这是非常有价值的资产。

如何解读答案

RTFM 和 STFW:如何知道你已完全搞砸了

有一个古老而神圣的传统:如果你收到RTFM (Read The Fucking Manual)的回应,回答者认为你应该去读他妈的手册。当然,基本上他是对的,你应该去读一读。

RTFM 有一个年轻的亲戚。如果你收到STFW(Search The Fucking Web)的回应,回答者认为你应该到他妈的网上搜索。那人多半也是对的,去搜索一下吧。(更温和一点的说法是 Google 是你的朋友!)

在论坛,你也可能被要求去爬爬论坛的旧文。事实上,有人甚至可能热心地为你提供以前解决此问题的讨论串。但不要依赖这种关照,提问前应该先搜索一下旧文。

通常,用这两句之一回答你的人会给你一份包含你需要内容的手册或者一个网址,而且他们打这些字的时候也正在读着。这些答复意味着回答者认为

  • 你需要的信息非常容易获得
  • 你自己去搜索这些信息比灌给你,能让你学到更多

你不应该因此不爽;依照黑客的标准,他已经表示了对你一定程度的关注,而没有对你的要求视而不见。你应该对他祖母般的慈祥表示感谢。

如果还是搞不懂

如果你看不懂回应,别立刻要求对方解释。像你以前试着自己解决问题时那样(利用手册,FAQ,网络,身边的高手),先试着去搞懂他的回应。如果你真的需要对方解释,记得表现出你已经从中学到了点什么。

比方说,如果我回答你:看来似乎是 zentry 卡住了;你应该先清除它。,然后,这是一个很糟的后续问题回应:zentry 是什么? 的问法应该是这样:哦~~~我看过说明了但是只有 -z 和 -p 两个参数中提到了 zentries,而且还都没有清楚的解释如何清除它。你是指这两个中的哪一个吗?还是我看漏了什么?

处理无礼的回应

很多黑客圈子中看似无礼的行为并不是存心冒犯。相反,它是直接了当,一针见血式的交流风格,这种风格更注重解决问题,而不是使人感觉舒服而却模模糊糊。

如果你觉得被冒犯了,试着平静地反应。如果有人真的做了出格的事,邮件列表、新闻群组或论坛中的前辈多半会招呼他。如果这没有发生而你却发火了,那么你发火对象的言语可能在黑客社区中看起来是正常的,而将被视为有错的一方,这将伤害到你获取信息或帮助的机会。

另一方面,你偶尔真的会碰到无礼和无聊的言行。与上述相反,对真正的冒犯者狠狠地打击,用犀利的语言将其驳得体无完肤都是可以接受的。然而,在行事之前一定要非常非常的有根据。纠正无礼的言论与开始一场毫无意义的口水战仅一线之隔,黑客们自己莽撞地越线的情况并不鲜见。如果你是新手或外人,避开这种莽撞的机会并不高。如果你想得到的是信息而不是消磨时光,这时最好不要把手放在键盘上以免冒险。

(有些人断言很多黑客都有轻度的自闭症或亚斯伯格综合症,缺少用于润滑人类社会正常交往所需的神经。这既可能是真也可能是假的。如果你自己不是黑客,兴许你认为我们脑袋有问题还能帮助你应付我们的古怪行为。只管这么干好了,我们不在乎。我们喜欢我们现在这个样子,并且通常对病患标记都有站得住脚的怀疑)。

Jeff Bigler 的观察总结和这个相关也值得一读 (tact filters)。

在下一节,我们会谈到另一个问题,当行为不当时所会受到的冒犯

如何避免扮演失败者

在黑客社区的论坛中有那么几次你可能会搞砸 —— 以本指南所描述到的或类似的方式。而你会在公开场合中被告知你是如何搞砸的,也许攻击的言语中还会带点夹七夹八的颜色。

这种事发生以后,你能做的最糟糕的事莫过于哀嚎你的遭遇、宣称被口头攻击、要求道歉、高声尖叫、憋闷气、威胁诉诸法律、向其雇主报怨、忘了关马桶盖等等。相反地,你该这么做:

熬过去,这很正常。事实上,它是有益健康且合理的。

社区的标准不会自行维持,它们是通过参与者积极而公开地执行来维持的。不要哭嚎所有的批评都应该通过私下的邮件传送,它不是这样运作的。当有人评论你的一个说法有误或者提出不同看法时,坚持声称受到个人攻击也毫无益处,这些都是失败者的态度。

也有其它的黑客论坛,受过高礼节要求的误导,禁止参与者张贴任何对别人帖子挑毛病的消息,并声称如果你不想帮助用户就闭嘴。 结果造成有想法的参与者纷纷离开,这么做只会使它们沦为毫无意义的唠叨与无用的技术论坛。

夸张的讲法是:你要的是“友善”(以上述方式)还是有用?两个里面挑一个。

记着:当黑客说你搞砸了,并且(无论多么刺耳)告诉你别再这样做时,他正在为关心他的社区而行动。对他而言,不理你并将你从他的生活中滤掉更简单。如果你无法做到感谢,至少要表现得有点尊严,别大声哀嚎,也别因为自己是个有戏剧性超级敏感的灵魂和自以为有资格的新来者,就指望别人像对待脆弱的洋娃娃那样对你。

有时候,即使你没有搞砸(或者只是在他的想像中你搞砸了),有些人也会无缘无故地攻击你本人。在这种情况下,抱怨倒是真的会把问题搞砸。

这些来找麻烦的人要么是毫无办法但自以为是专家的不中用家伙,要么就是测试你是否真会搞砸的心理专家。其它读者要么不理睬,要么用自己的方式对付他们。这些来找麻烦的人在给他们自己找麻烦,这点你不用操心。

也别让自己卷入口水战,最好不要理睬大多数的口水战 – 当然,这是在你检验它们只是口水战,并且未指出你有搞砸的地方,同时也没有巧妙地将问题真正的答案藏于其后(这也是有可能的)。

不该问的问题

以下是几个经典蠢问题,以及黑客没回答时心中所想的:

问题:我能在哪找到 X 程序或 X 资源?

问题:我怎样用 X 做 Y?

问题:如何设定我的 shell 提示?

问题:我可以用 Bass-o-matic 文件转换工具将 AcmeCorp 档案转换为 TeX 格式吗?

问题:我的程序/设定/SQL 语句没有用

问题:我的 Windows 电脑有问题,你能帮我吗?

问题:我的程序不会动了,我认为系统工具 X 有问题

问题:我在安装 Linux(或者 X )时有问题,你能帮我吗?

问题:我怎么才能破解 root 帐号/窃取 OP 特权/读别人的邮件呢?


问题:我能在哪找到 X 程序或 X 资源?

回答:就在我找到它的地方啊,白痴 —— 搜索引擎的那一头。天哪!难道还有人不会用 Google 吗?

问题:我怎样用 X 做 Y?

回答:如果你想解决的是 Y ,提问时别给出可能并不恰当的方法。这种问题说明提问者不但对 X 完全无知,也对 Y 要解决的问题糊涂,还被特定形势禁锢了思维。最好忽略这种人,等他们把问题搞清楚了再说。

问题:如何设定我的 shell 提示??

回答:如果你有足够的智慧提这个问题,你也该有足够的智慧去 RTFM,然后自己去找出来。

问题:我可以用 Bass-o-matic 文件转换工具将 AcmeCorp 档案转换为 TeX 格式吗?

回答:试试看就知道了。如果你试过,你既知道了答案,就不用浪费我的时间了。

问题:我的{程序/设定/SQL 语句}不工作

回答:这不算是问题吧,我对要我问你二十个问题才找得出你真正问题的问题没兴趣 —— 我有更有意思的事要做呢。在看到这类问题的时候,我的反应通常不外如下三种

  • 你还有什么要补充的吗?
  • 真糟糕,希望你能搞定。
  • 这关我有什么屁事?

问题:我的 Windows 电脑有问题,你能帮我吗?

回答:能啊,扔掉微软的垃圾,换个像 Linux 或 BSD 的开源操作系统吧。

注意:如果程序有官方版 Windows 或者与 Windows 有互动(如 Samba),你可以问与 Windows 相关的问题, 只是别对问题是由 Windows 操作系统而不是程序本身造成的回复感到惊讶, 因为 Windows 一般来说实在太烂,这种说法通常都是对的。

问题:我的程序不会动了,我认为系统工具 X 有问题

回答:你完全有可能是第一个注意到被成千上万用户反复使用的系统调用与函数库档案有明显缺陷的人,更有可能的是你完全没有根据。不同凡响的说法需要不同凡响的证据,当你这样声称时,你必须有清楚而详尽的缺陷说明文件作后盾。

问题:我在安装 Linux(或者 X )时有问题,你能帮我吗?

回答:不能,我只有亲自在你的电脑上动手才能找到毛病。还是去找你当地的 Linux 使用群组者寻求实际的指导吧(你能在这儿找到使用者群组的清单)。

注意:如果安装问题与某 Linux 的发行版有关,在它的邮件列表、论坛或本地使用者群组中提问也许是恰当的。此时,应描述问题的准确细节。在此之前,先用 Linux所有被怀疑的硬件作关键词仔细搜索。

问题:我怎么才能破解 root 帐号/窃取 OP 特权/读别人的邮件呢?

回答:想要这样做,说明了你是个卑鄙小人;想找个黑客帮你,说明你是个白痴!

好问题与蠢问题

最后,我将透过举一些例子,来说明怎样聪明的提问;同一个问题的两种问法被放在一起,一种是愚蠢的,另一种才是明智的。

蠢问题

我可以在哪儿找到关于 Foonly Flurbamatic 的资料?

这种问法无非想得到 STFW 这样的回答。

聪明问题

我用 Google 搜索过 “Foonly Flurbamatic 2600”,但是没找到有用的结果。谁知道上哪儿去找对这种设备编程的资料?

这个问题已经 STFW 过了,看起来他真的遇到了麻烦。

蠢问题

我从 foo 项目找来的源码没法编译。它怎么这么烂?

他觉得都是别人的错,这个傲慢自大的提问者。

聪明问题

foo 项目代码在 Nulix 6.2 版下无法编译通过。我读过了 FAQ,但里面没有提到跟 Nulix 有关的问题。这是我编译过程的记录,我有什么做的不对的地方吗?

提问者已经指明了环境,也读过了 FAQ,还列出了错误,并且他没有把问题的责任推到别人头上,他的问题值得被关注。

蠢问题

我的主机板有问题了,谁来帮我?

某黑客对这类问题的回答通常是:好的,还要帮你拍拍背和换尿布吗?,然后按下删除键。

聪明问题

我在 S2464 主机板上试过了 X 、 Y 和 Z ,但没什么作用,我又试了 A 、 B 和 C 。请注意当我尝试 C 时的奇怪现象。显然 florbish 正在 grommicking,但结果出人意料。通常在 Athlon MP 主机板上引起 grommicking 的原因是什么?有谁知道接下来我该做些什么测试才能找出问题?

这个家伙,从另一个角度来看,值得去回答他。他表现出了解决问题的能力,而不是坐等天上掉答案。

在最后一个问题中,注意告诉我答案给我启示,指出我还应该做什么诊断工作之间微妙而又重要的区别。

事实上,后一个问题源自于 2001 年 8 月在 Linux 内核邮件列表(lkml)上的一个真实的提问。我(Eric)就是那个提出问题的人。我在 Tyan S2464 主板上观察到了这种无法解释的锁定现象,列表成员们提供了解决这一问题的重要信息。

通过我的提问方法,我给了别人可以咀嚼玩味的东西;我设法让人们很容易参与并且被吸引进来。我显示了自己具备和他们同等的能力,并邀请他们与我共同探讨。通过告诉他们我所走过的弯路,以避免他们再浪费时间,我也表明了对他们宝贵时间的尊重。

事后,当我向每个人表示感谢,并且赞赏这次良好的讨论经历的时候, 一个 Linux 内核邮件列表的成员表示,他觉得我的问题得到解决并非由于我是这个列表中的人,而是因为我用了正确的方式来提问。

黑客从某种角度来说是拥有丰富知识但缺乏人情味的家伙;我相信他是对的,如果我个乞讨者那样提问,不论我是谁,一定会惹恼某些人或者被他们忽视。他建议我记下这件事,这直接导致了本指南的出现。

如果得不到回答

如果仍得不到回答,请不要以为我们觉得无法帮助你。有时只是看到你问题的人不知道答案罢了。没有回应不代表你被忽视,虽然不可否认这种差别很难区分。

总的来说,简单的重复张贴问题是个很糟的点子。这将被视为无意义的喧闹。有点耐心,知道你问题答案的人可能生活在不同的时区,可能正在睡觉,也有可能你的问题一开始就没有组织好。

你可以通过其他渠道获得帮助,这些渠道通常更适合初学者的需要。

有许多网上的以及本地的使用者群组,由热情的软件爱好者(即使他们可能从没亲自写过任何软件)组成。通常人们组建这样的团体来互相帮助并帮助新手。

另外,你可以向很多商业公司寻求帮助,不论公司大还是小。别为要付费才能获得帮助而感到沮丧!毕竟,假使你的汽车发动机汽缸密封圈爆掉了 —— 完全可能如此 —— 你还得把它送到修车铺,并且为维修付费。就算软件没花费你一分钱,你也不能强求技术支持总是免费的。

对像是 Linux 这种大众化的软件,每个开发者至少会对应到上万名使用者。根本不可能由一个人来处理来自上万名使用者的求助电话。要知道,即使你要为这些协助付费,和你所购买的同类软件相比,你所付出的也是微不足道的(通常封闭源代码软件的技术支持费用比开源软件的要高得多,且内容也没那么丰富)。

如何更好地回答问题

态度和善一点。问题带来的压力常使人显得无礼或愚蠢,其实并不是这样。

对初犯者私下回复。对那些坦诚犯错之人没有必要当众羞辱,一个真正的新手也许连怎么搜索或在哪找常见问题都不知道。

如果你不确定,一定要说出来!一个听起来权威的错误回复比没有还要糟,别因为听起来像个专家很好玩,就给别人乱指路。要谦虚和诚实,给提问者与同行都树个好榜样。

如果帮不了忙,也别妨碍他。不要在实际步骤上开玩笑,那样也许会毁了使用者的设置 —— 有些可怜的呆瓜会把它当成真的指令。

试探性的反问以引出更多的细节。如果你做得好,提问者可以学到点东西 —— 你也可以。试试将蠢问题转变成好问题,别忘了我们都曾是新手。

尽管对那些懒虫抱怨一声 RTFM 是正当的,能指出文件的位置(即使只是建议个 Google 搜索关键词)会更好。

如果你决定回答,就请给出好的答案。当别人正在用错误的工具或方法时别建议笨拙的权宜之计(wordaround),应推荐更好的工具,重新界定问题。

正面的回答问题!如果这个提问者已经很深入的研究而且也表明已经试过 X 、 Y 、 Z 、 A 、 B 、 C 但没得到结果,回答 试试看 A 或是 B 或者 试试 X 、 Y 、 Z 、 A 、 B 、 C 并附上一个链接一点用都没有。

帮助你的社区从问题中学习。当回复一个好问题时,问问自己如何修改相关文件或常见问题文件以免再次解答同样的问题?,接着再向文件维护者发一份补丁。

如果你是在研究一番后才做出的回答,展现你的技巧而不是直接端出结果。毕竟授人以鱼不如授人以渔

相关资源

如果你需要个人电脑、Unix 系统和网络如何运作的基础知识,参阅 Unix 系统和网络基本原理

当你发布软件或补丁时,试着按软件发布实践操作。

在2019年5月17号,莫斯科的 DevGAMM上,Jonathan Blow发表了一个题为『阻止文明倒塌』的长达一小时的奇怪演讲。我把这个演讲总结为下面这篇文字记录。

善意提醒:不懂编程不代表你我不能阅读一篇和计算机有关的演讲,并有所思考,只能跟风刷刷梗;另外,对于Blow的观点,作为成年人我们应该可以有自己的反思。

我个人对这个演讲的逻辑非常赞叹,但也认为他明显偏重了软件系统本身的问题,而对另一个甚至更根本的问题选择了视而不见。那就是当代效率提升过程中的真正瓶颈:人的协作。如果仔细观察软件,以及其他可以类比的当代文明复杂系统,大规模的人际协作中无法改进的沟通效率、责任分配、人性的不可管理,都让系统不可避免地显现为笨拙甚至荒谬的面貌。而抽象层级的提高,也绝不仅仅是软件的趋势而已,正如它所带来的问题也绝不仅是机器的问题。

阻止文明倒塌

乔纳森布洛,2019年5月在莫斯科

演讲从1957年苏联发射 Sputnik 上天引发的美苏太空争霸谈起,回顾了首次卫星发射、首次宇航员上天,以及首次登月的过程。在1962年9月肯尼迪总统在一次国会演讲中宣称我们要在这个十年结束之前实现登月。结果1969年阿波罗11号实现了这个目标。Blow 的要点在于:人类(这里指美国)从一无所知,到实现登月只用了12年。然而从那时之后,人类的载人航天事业很大程度上停滞,甚至出现了倒退。这是不是很令人遗憾?

Blow 播放了一段后来解密的阿波罗11号现场纪录片片段,那种震撼的感觉难以名状,只有看的人能了解其中直观的『退步感』。

然后 Blow 播放了一段 Elon Musk 的 TED 访谈,其中 Elon 指出:我们一定要意识到:不同于可持续能源的未来是必然的,Spacefare civilization(多星球文明)绝对不是必然会发生的(它需要巨大的努力)。1969年人类可以登月,然后人类只能用航天飞机把人送到近地轨道。然后航天飞机退役,近地轨道也没法送上去了——连起来看,趋势是向着0倒退。这和人们对于“科技当然显然必然自动地向前进步”这样的信念相违背。

Elon 认为,正好相反,科技在不付出巨大努力的情况下,是逐渐倒退的

从古埃及的后人忘记金字塔,到古罗马人忘记如何建造罗马水道。

Elon 的火箭创业公司则非常成功,他们的目标是2024年重新实现载人登月。Blow 接下来给出了几个科技倒退的实例:

第一个是 Lycurgus Cup,公元300年。从正面看它的反光是青色的,透光来看,则呈现为红色。原因是其银和金的颗粒体积非常小,小到50-70纳米,如此之小以至于物理放大镜看不见它们,想观测需要用电子显微镜。然后西罗马帝国覆灭,后人忘记了这个工艺

很多人认为这就是扯淡,当时的人并不知道其任何原理只是误打误撞做出了这些东西。Blow 则认为有如此观点的人显然没有用心亲手打磨过任何东西,否则他们一定会明白,达到这样水平的结果,是不可能通过偶然和运气的,对于工艺的掌握必然在不断迭代过程中达到了非常高的水平,它才有任何可能出现。比如这个图案中人的身体的肉红色,肯定是费劲千辛万苦不断改进实现出来的。这就是一种材料科学。

然后 Blow 提到了拜占庭的喷火筒。拜占庭的战船上安装了这种金属喷管,喷出某种成分的“水扑不灭”的有机物燃烧导致的火焰,焚毁敌人船只。这是拜占庭帝国的国家机密,它在帝国灭亡之前就被遗忘,原因不明,具体实现方式已经不可考证。

第三个例子是机械日历,一种安提凯希拉装置(Antikythera Mechanism),1901年于希腊安提凯特拉岛上的一艘古船残骸中被发现的随船沉没2100余年的钟形装置。这个机械日历的实际模型被复原为一段动画(自己看视频,语言无法描述),它的精密程度是高档机械手表的范畴。它的存在意味着背后多么复杂的一整套制造能力呢?

这样的例子还有很多,就不说了,Blow 这时候放了地动仪的图片。要点是:在人类历史上,杰出的科技被完全遗忘,这件事经常发生。当代也一样。

然后 Blow 给出的例子是 Bob Colwell,早期英特尔的首席芯片架构师,在计算机发展初期的时候接受采访的片段内容,背景是他们发现合作的零部件制造商TI(德州仪器公司)送来的产品不能稳定使用,质量残次。

Bob 去找 TI 质询,本来以为对方会说“那是你们不知道怎么使用,我们的产品是好的”,实际对方的答复是:“是,我们知道,我看看你的清单。哦,我们还有更多你们不知道的(不好使的产品)。”实际情况是,TI 没有比任何其他竞争对手更差,摩托罗拉、Fairchild 也一样。这些硅制品让英特尔的芯片研发停滞不前,为何会如此?TI的人回答道:“第一代 TTL(逻辑门电路)是那帮胡子花白的老头子做的,他们知道其中的道理;现在的工程师都是毛头小子,学校毕业过来搞生产,他们不知道内部组装的改变,会导致感应峰(inductive spikes)。”这里的spike就是指每一个点的电压变化时,都会产生磁场,而磁场变化的相互干扰,没有被设计者纳入考虑,因为他们不理解。

这就是科技退步的原因。代际之间的交流和传承需要巨大的努力,这过程中有损失。如果代际的传承失败,文明就灭亡。

接下来 Blow 给出了历史中文明灭亡的实例,来自于 Eric Clive 的一个演讲,题目是《公元前1700年的文明灭亡》,具体来说是青铜器时代晚期,爱琴海,古埃及,塞浦路斯,迈锡尼,赫梯,古巴比伦等。这一圈围绕在美索不达米亚平原和地中海一带的文明曾经形成了一个非常复杂的贸易网络。在下图中,每个节点是一个文明,不是每一个都能和每一个贸易,但通过中间节点,所有贸易都达到非常优化的效率。

这一点非常重要,因为青铜作为国防和生产的重要资料,非常难以制造。你需要用锡和铜一起生产。铜产地比较少,比如塞浦路斯岛。而锡则更难找,而且离铜产地非常远,比如阿富汗。而这些文明需要把它们运到一起以生产青铜。

而这些文明的灭亡至今没有确切原因,有人说是自然环境恶化导致互相攻击,贸易变成战争。它们灭亡到什么程度呢,不仅是国家不存在了,城市也消失了,语言和文化也消失了。刻在石碑上的文字大多数至今我们不能翻译。

Blow 的要点是:软件正在倒退,而人类空前依赖软件。尽管,这和我们置身其中的人的观感是相反的。波音飞机掉下来的主要原因就是软件问题(编者不能同意)。文明衰退的速度如果很慢,我们能意识到这个衰退么?

这里 Blow 给出了他对于“软件明显在蓬勃发展”之直观感受的解释:软件正在享受硬件能力提升的红利,它只是“看上去”蓬勃发展而已。

机器学习是最明显的例子。一方面,它在二十年前不能存在主要是因为硬件性能无法支持而已。另一方面,它只是人类依赖着的软件世界中极小极小的部分。属于软件的进步已经很长时间停滞。比如,我们使用软件给自己AI换脸成明星的样子,或者配上猪耳朵,这个有趣的部分,只占这个app极小极小的部分,而这个部分非常简单,另外的极大部分却极度复杂,包括把你的脸加载到屏幕上,以及处理你的点击,等等。出问题的是这些部分。人工智能的局部成就,没法和软件世界整体的退化的巨大惯性抗衡。

最明显的迹象,就是“我们已经不指望软件能长期稳定工作了。”我们的标准一降再降,还能降到多低?降低到哪里会出大问题?

接下来 Blow 疯狂吐槽了十分钟,关于电脑上一切软件都经常 bug,以至于用户对于一切软件“重启试试”成为不假思索的操作。包括 Emacs 的问题、Visual Studio 不能处理最最简单的指令,只能连续报错,微软 Word 的字符换行 bug 二十年后仍然没有消灭,像幽灵一样此起彼伏;于是他为了缓解自己的愤怒想打游戏,打开 Epic Store 和 Steam 又连续遇到 bug;于是他关掉游戏客户端去看 CS 直播,发现 CS 直播电竞比赛里全程有一个多余的名为“未定义”的 bug 玩家出现在地图里,直到比赛结束;然后他进入俄罗斯签证申请页面,被死活不能通过,且不能刷新的手机号验证脚本逼得只能刷新整个页面重新填写申请;然后他来到莫斯科住进酒店,他的房间的座机有5%的概率会空调开关触发响铃……等等。编者这里就不赘述了。

这让90年代电脑销售的一个常见推销语“五个9”(指,本设备可以99.999%时间稳定运行)成为今日的讽刺。Blow的电脑连两个9都没有。

对于这个问题,软件行业的普遍答案是:市场不会为更好的稳定性买单。Blow则认为:对于一个从未提供出足够稳健的产品的行业,为什么会有人会相信它『能』?

接下来,Blow 从正面描述了软件的结构:抽象层次的序列。机器语言、汇编语言、Fortran/C/C++、C#/Haskell/Javascript……在这个序列里,绝大多数工程师在最高抽象层次进行工作,因为这是“聪明、省力、高效率的”。在这个抽象从低到高的序列里的某些位置,问题就出在这里。Blow 认为,全行业的高抽象层次工作,多数人的“高效率”的另一面,是失去(或者从未拥有过)能力。考虑 Facebook 作为一个软件的功能增加,和它要多雇佣的成千上万的工程师,二者相除得到的平均每人的价值创造是趋近于零的。这和“抽象层次越高效率越高”明显矛盾。

Blow 给出的对比,是 UNIX 发明者 Ken Thompson 的一个演讲片段。他回忆道他趁老婆孩子出去度假,用了三个礼拜写了三个东西(编辑器、编译器、软硬件交互层或者叫操作系统),做出了 UNIX。现场爆发笑声和掌声——当今程序员可没有这种效率。而 Blow 认为这是个悲剧,它并不好笑。今天软件显没有在进步,它的鲁棒性和生产力都在衰退。

Blow 从图形计算的角度给出了一些“最最基本的,你不能直接做的事情”,其中一个编者能听懂的例子是,把一个程序拷贝到另一个设备(安装是个复杂过程)——而这并不是因为 CPU,CPU 不能背锅因为今天各类设备的 CPU 一致性已经相当好。安装程序不是为了对接 CPU,而是为了解决操作系统层面不可思议之多的兼容问题,其绝大部分是我们不想打交道的。操作系统本来是给 CPU 赋予能力,但同时你也可以说它防止着 CPU 具备很多能力。在编程过程中,你绝大多数时间在处理那些,你很难理解、也不可能预料到的各种和你的设计思路没有关系的问题。

更可怕的是,你不能直接编写一个独立程序进行编译和 linking。微软为了能让人这么做,专门设计了一个 vswhere 的软件。

现在有一种叫做LSP的东西,开源的语言服务器协定。在Blow看来它基本上是一种更复杂、更费劲的编写和调用库的方式,而其满足的需求都是非常基本的操作,比如在你的编辑器里临时起意,查看一个变量的特征或者类型,LSP给你提供一个工具条,或者鼠标点击直接查询的功能。为了实现这个,你需要一个标准化服务跑在服务器上,好让你的编辑器和服务器用socket交互——为了使用方便,你把本地的、单一的编程工作置于分布式的系统之中。

这样的做法推而广之,你依赖了越来越多的库,库又依赖了越来越多的库,关键是这些库/服务本身可能是在变化的、并且没有被中心化地存储和管理,这样一来我们就陷入了无穷无尽的debug的、重启服务的、互相同步的……我们自身所不能掌握的问题海洋之中。

现在大家竟然在积极主动建设着这样一个东西。程序员们忙着把简单事情复杂化的同时……

电脑游戏却变得连最基本的事情都很难实现了。比如游戏现在很难保持全屏, 经常跳出为窗口。再比如,辛辛苦苦做的游戏,很难稳定在一个帧数上跑,无论你多努力也不行。Allen(全称听不清楚)在GDC上介绍了他的一篇论文讲述了这个简单的能力我们现在无法具备的原因。

复杂性的提高,加速了知识的丢失:

1、知识总量更多,我们就让每个人知道的比例变得更小来应对。

每个人对全局的把握一弱再弱,既难以传承知识,也难以做好自己的工作。

2、『深知识』被『琐碎信息』替代。

典型的深知识,如理解Cache Coherency(缓存一致性,指保留在高速缓存中的共享资源,保持数据一致性的机制),可以让人优化程序在多个处理器上跑得更快。典型的琐碎信息,如『这个Unity里的小图标不知道为啥不显示了,请教老师傅得知,是某个深藏不露的菜单里看似没有关联的一个开关关闭了导致的,打开就好了,过了一阵子它不知道为何又关闭了,总之,编译之前一定要记得检查那开关打开没有』。后者作为知识,往往几个月后随着Unity版本更新就完全作废,作为程序员要花很多脑力学习这种速朽的所谓知识,这件事情非常缺(offensive)。

3、好信息被噪音淹没。

症状是谷歌越来越难用,你的问题越复杂,网上搜来搜去看到的答案就越大概率是错的,或者只是浪费你的时间。

Blow认为,显而易见的是,复杂性越高,我们承受灾难,或者体制性腐化(参考上文东罗马帝国),的能力就越差。而现在大家似乎相信,我们能承担的复杂性上限,是无限的。想象现在大公司里那种极少数能够透彻理解整个系统的工程师离退休之后后继无人、且很难把整套知识传递给年轻员工的情况,答案已经很明显了。

这一切和游戏有什么关系呢?

曾经的游戏,是关于如何榨干性能,将机器的潜力推至极限的。推至极限,要求我们透彻理解机器本身,这就自然导致我们倾向于做出非常鲁棒的程序。

今天整个行业转向Unity和Unreal。自己写引擎的人越来越少。一整代程序员从学习到工作,一直在写支离破碎的C#片段,放在Unity的这里那里,从来没写过系统性的,或者底层的程序。这本身倒没什么,它缩短开发时间,提高个人效率,缩短游戏开发时间。但这意味着放弃,对其他能力的放弃,对整体性知识的放弃。

Blow说,割裂性的分工没问题,但是如果所有人都这样分工,那么没有任何人会做这之外的事情了。Unity和Unreal又是怎么来的呢?它们生长于这样一个环境:所有的游戏公司都在自己写引擎,专门的引擎公司就从这些游戏公司里雇人过来,做出了如今的垄断性引擎。现在没有这样的环境,彻底没有那种人了的话,Unity去哪里雇人?首先的结果,是他们彻底断绝了新引擎作为竞争对手的涌现可能。然后,这几个大引擎怎么继续维护和发展,也成了问题,自身开始衰退,也是很有可能的。

游戏开发者社群的情况很像阿西莫夫的《基地》:我们有一群人知道怎么用电脑编程、也有一群人精通嵌入式系统/高性能计算这些事。当很多程序败坏、丢失、难以维护的时候,我们这群人的知识可以重建它们——但我(Blow)真的不确定,我们是否有足够多的底层人才,以及对底层充分了解的高层人才,能做好系统底层的工作。也许,我们需要一个新的『基地』(foundation,也有基础的意思)。不好意思,剧透了。

说回到青铜时代的文明失落,无数历史显示了一种现象:当只有精英阶层可以读和写的时候,很多能力无法普及,文明变得脆弱——普通人也主观上不想学习那些。今天的我们,几乎没有人能理解自己的程序跑着的时候,CPU里正在发生什么。这就是脆弱。

如果青铜器时代那个程度的知识都会失落,那面对我们现在如此复杂的系统,我们怎么能相信它的存续和发展?各种各样的生存压力随时可能爆发,考验我们的软件世界。比如气候变迁。比如国际争端,比如一些国家切断国际互联网了,比如中国不给我们加工芯片了。别笑,现在有些国家的程序员连上stackoverflow复制粘贴代码都费劲。这些事情单独来看肯定都不至于伤害我们的文明,系统复杂到今天程度,抗折腾能力会很差。

正如Elon Musk所言,技术会自然地退步。我们要警惕,要对抗,就一定要在每一个层级上简化:硬件、操作系统、库、应用层面的代码、网络、编译、debug、内容分发、人机交互。而这一切又完全可以如此简单,因为它们的现状如此荒谬!

我们需要的只是『意愿』(will)和『品位』(taste)而已!

大家都充分认识到复杂之荒谬,简单之美——简化可以让事情变得更好。

退一万步讲,也许你认为软件不会让文明倒塌。没关系,咱们就只从自身利益出发——所有程序员都每天气呼呼的工作着,因为他们知道自己大量的时间精力用在没有意义的荒唐事上,而不是真正有趣的,创造性的事上。如果我们不改变做事的方式,未来的程序员工作会更加庸俗不堪,就跟没有SpaceX时候的美国航天事业一样。

再退一步,假设你只是独立游戏开发者,你可能觉得,自己无力改变任何事情,只要忍几个月,把游戏做完就好了,毕竟重构、重写需要大量的时间——我想告诉你这种想法永远是错误的,因为我们永远低估了自己做游戏需要的时间,你以为的五个月,大概率实际是几年。糟糕的工作方式让你付出的代价,比你以为的,要大得多,长得多。也许那拖延的几年时间,就是拜复杂系统所赐。反过来,如果你积极应战,简化架构和代码,兴许很值得。听起来像是又基础又无聊的建议,但我要大胆断言——我们大多数人甚至都不太知道怎么做这件事了。

最后Blow给大家提供了三个补充阅读/观看的资料,

  • Casey Muratori,『三千万行代码难题』。链接在此

  • Samo Burja,『文明:体制、知识与未来』。链接在此

  • Eric Cline,『公元前1177:文明倒塌时』链接在此

动态规划

建议观看 MIT 算法导论-动态规划中的课程。

适用于动态规划的问题,需要满足最优子结构无后效性,动态规划的求解过程,在于找到状态转移方程,进行自底向上的求解。

例题

爬楼梯问题 LeetCode 70

经典的动态规划问题之一,容易找到其状态转移方程为 dp[i] = dp[i-1] + dp[i-2],从基础的 1 和 2 个台阶两个状态开始,自底向上求解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int climbStairs(int n) {
if (n == 1) {
return 1;
}

int* dp = new int[n+1]();
dp[1] = 1;
dp[2] = 2;

for (int i = 3; i <= n; i++) {
dp[i] = dp[i-1] + dp[i-2];
}

return dp[n];
}

从上面的代码中看到,dp[i] 只依赖 dp[i-1]dp[i-2],因此可以将代码简化:

1
2
3
4
5
6
7
8
9
int climbStairs(int n) {
int f0 = 1, f1 = 1, i, f2;
for (i=2; i<=n; i++) {
f2 = f0 + f1;
f0 = f1;
f1 = f2;
}
return f1;
}

容易看出其实结果就是 fibonacci 数列的第 n 项。

连续子数组的最大和 LeetCode 53

dp[n] 表示元素 n 作为末尾的连续序列的最大和,容易想到状态转移方程为dp[n] = max(dp[n-1] + num[n], num[n]),从第 1 个元素开始,自顶向上求解:

1
2
3
4
5
6
7
8
9
10
11
12
13
int maxSubArray(vector<int>& nums) {
int* dp = new int[nums.size()]();

dp[0] = nums[0];
int result = dp[0];

for (int i = 1; i < nums.size(); i++) {
dp[i] = max(dp[i-1] + nums[i], nums[i]);
result = max(result, dp[i]);
}

return result;
}

类似前一个问题,这个问题当中,求解 dp[i] 只依赖 dp[i-1],因此可以使用变量来存储,简化代码:

1
2
3
4
5
6
7
8
9
int maxSubArray(int A[], int n) {
int result = INT_MIN;
int f = 0;
for (int i=0; i < n; i++) {
f = max(f + A[i], A[i]);
result = max(result, f);
}
return result;
}

House Robber LeetCode 198

对于一个房子,有抢和不抢两种选择,容易得到状态转移方程 dp[i+1] = max(dp[i-1] + nums[i], dp[i]),示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int rob(vector<int>& nums) {
int n = nums.size();
if (n == 0) {
return 0;
}

vector<int> dp = vector<int>(n + 1);

dp[0] = 0;
dp[1] = nums[0];

for (int i = 1; i < nums.size(); i++) {
int v = nums[i];
dp[i+1] = max(dp[i-1] + v, dp[i]);
}

return dp[n];
}

同样的,可以使用两个变量简化代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int rob(vector<int>& nums) {
int n = nums.size();
if (n == 0) {
return 0;
}

int prev1 = 0;
int prev2 = 0;

for (int i = 0; i < nums.size(); i++) {
int v = nums[i];
int temp = prev1;
prev1 = max(prev2 + v, prev1);
prev2 = temp;
}

return prev1;
}

最长回文子串 LeetCode 5

dp[i][j] 表示子串 i 到 j 是否是回文,使用动态规划求解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
string longestPalindrome(string s) {
int m = s.size();
if (m == 0) {
return "";
}
vector<vector<int>> dp(m, vector<int>(m, 0));
int start = 0;
int length = 1;

for (int i = 0; i < m; i++) {
// 单个字符属于回文,例如 abcd
dp[i][i] = 1;

// 连续两个字符相同属于回文,例如 abb
if (i < m - 1) {
if (s[i] == s[i + 1]) {
dp[i][i + 1] = 1;
start = i;
length = 2;
}
}
}

for (int len = 2; len <= m; len++) {
for (int i = 0; i < m - len; i++) {
int j = i + len;
// 扩展长度
if (dp[i + 1][j - 1] == 1 && s[i] == s[j]) {
dp[i][j] = 1;

if (j - i + 1 > length) {
start = i;
length = j - i + 1;
}
}
}
}

return s.substr(start, length);
}

最小编辑距离 LeetCode 72

dp[i][j] 表示从 word[0..i) 转换到 word[0..j) 的最小操作,使用动态规划求解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
int minDistance(string word1, string word2) {
int m = word1.size();
int n = word2.size();
vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));

// 全部删除,操作数量为 i
for (int i = 0; i <= m; i++) {
dp[i][0] = i;
}

for (int j = 0; j <= n; j++) {
dp[0][j] = j;
}

for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
// 末尾字符相同,不需要编辑
if (word1[i - 1] == word2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1];
} else {
// 末尾字符不同,三种编辑情况,取最小值
dp[i][j] = min(dp[i - 1][j - 1], min(dp[i][j - 1], dp[i - 1][j])) + 1;
}
}
}

return dp[m][n];
}

HTTP

HTTP 的特性

  • HTTP 协议构建于 TCP/IP 协议之上,是一个应用层协议,默认端口号是 80
  • HTTP 是无连接无状态

HTTP 报文

请求报文

HTTP 协议是以 ASCII 码传输,建立在 TCP/IP 协议之上的应用层规范。规范把 HTTP 请求分为三个部分:状态行、请求头、消息主体。类似于下面这样:

<method> <request-URL> <version>
<headers>

<entity-body>

HTTP 定义了与服务器交互的不同方法,最基本的方法有4种,分别是GETPOSTPUTDELETEURL全称是资源描述符,我们可以这样认为:一个URL地址,它用于描述一个网络上的资源,而 HTTP 中的GETPOSTPUTDELETE就对应着对这个资源的查,增,改,删4个操作。

  1. GET 用于信息获取,而且应该是安全的 和 幂等的。

    所谓安全的意味着该操作用于获取信息而非修改信息。换句话说,GET 请求一般不应产生副作用。就是说,它仅仅是获取资源信息,就像数据库查询一样,不会修改,增加数据,不会影响资源的状态。

    幂等的意味着对同一 URL 的多个请求应该返回同样的结果。

    GET 请求报文示例:

    GET /books/?sex=man&name=Professional HTTP/1.1
    Host: www.example.com
    User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
    Gecko/20050225 Firefox/1.0.1
    Connection: Keep-Alive
  2. POST 表示可能修改变服务器上的资源的请求。

POST / HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
Gecko/20050225 Firefox/1.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 40
Connection: Keep-Alive

sex=man&name=Professional  
  1. 注意:

    • GET 可提交的数据量受到URL长度的限制,HTTP 协议规范没有对 URL 长度进行限制。这个限制是特定的浏览器及服务器对它的限制
    • 理论上讲,POST 是没有大小限制的,HTTP 协议规范也没有进行大小限制,出于安全考虑,服务器软件在实现时会做一定限制
    • 参考上面的报文示例,可以发现 GET 和 POST 数据内容是一模一样的,只是位置不同,一个在 URL 里,一个在 HTTP 包的包体里

POST 提交数据的方式

HTTP 协议中规定 POST 提交的数据必须在 body 部分中,但是协议中没有规定数据使用哪种编码方式或者数据格式。实际上,开发者完全可以自己决定消息主体的格式,只要最后发送的 HTTP 请求满足上面的格式就可以。

但是,数据发送出去,还要服务端解析成功才有意义。一般服务端语言如 PHP、Python 等,以及它们的 framework,都内置了自动解析常见数据格式的功能。服务端通常是根据请求头(headers)中的 Content-Type 字段来获知请求中的消息主体是用何种方式编码,再对主体进行解析。所以说到 POST 提交数据方案,包含了 Content-Type 和消息主体编码方式两部分。下面就正式开始介绍它们:

  • application/x-www-form-urlencoded

这是最常见的 POST 数据提交方式。浏览器的原生 <form> 表单,如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urlencoded 方式提交数据。上个小节当中的例子便是使用了这种提交方式。可以看到 body 当中的内容和 GET 请求是完全相同的。

  • multipart/form-data

这又是一个常见的 POST 数据提交的方式。我们使用表单上传文件时,必须让 <form> 表单的 enctype 等于 multipart/form-data。直接来看一个请求示例:

POST http://www.example.com HTTP/1.1
Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryrGKCBY7qhFd3TrwA

------WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name="text"

title
------WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name="file"; filename="chrome.png"
Content-Type: image/png

PNG ... content of chrome.png ...
------WebKitFormBoundaryrGKCBY7qhFd3TrwA--

这个例子稍微复杂点。首先生成了一个 boundary 用于分割不同的字段,为了避免与正文内容重复,boundary 很长很复杂。然后 Content-Type 里指明了数据是以 multipart/form-data 来编码,本次请求的 boundary 是什么内容。消息主体里按照字段个数又分为多个结构类似的部分,每部分都是以 –boundary 开始,紧接着是内容描述信息,然后是回车,最后是字段具体内容(文本或二进制)。如果传输的是文件,还要包含文件名和文件类型信息。消息主体最后以 –boundary– 标示结束。关于 multipart/form-data 的详细定义,请前往 RFC1867 查看(或者相对友好一点的 MDN 文档)。

这种方式一般用来上传文件,各大服务端语言对它也有着良好的支持。

上面提到的这两种 POST 数据的方式,都是浏览器原生支持的,而且现阶段标准中原生 <form> 表单也只支持这两种方式(通过 <form> 元素的 enctype 属性指定,默认为 application/x-www-form-urlencoded。其实 enctype 还支持 text/plain,不过用得非常少)。

随着越来越多的 Web 站点,尤其是 WebApp,全部使用 Ajax 进行数据交互之后,我们完全可以定义新的数据提交方式,例如 application/jsontext/xml,乃至 application/x-protobuf 这种二进制格式,只要服务器可以根据 Content-TypeContent-Encoding 正确地解析出请求,都是没有问题的。

响应报文

HTTP 响应与 HTTP 请求相似,HTTP响应也由3个部分构成,分别是:

  • 状态行
  • 响应头(Response Header)
  • 响应正文

状态行由协议版本、数字形式的状态代码、及相应的状态描述,各元素之间以空格分隔。

常见的状态码有如下几种:

  • 200 OK 客户端请求成功
  • 301 Moved Permanently 请求永久重定向
  • 302 Moved Temporarily 请求临时重定向
  • 304 Not Modified 文件未修改,可以直接使用缓存的文件。
  • 400 Bad Request 由于客户端请求有语法错误,不能被服务器所理解。
  • 401 Unauthorized 请求未经授权。这个状态代码必须和WWW-Authenticate报头域一起使用
  • 403 Forbidden 服务器收到请求,但是拒绝提供服务。服务器通常会在响应正文中给出不提供服务的原因
  • 404 Not Found 请求的资源不存在,例如,输入了错误的URL
  • 500 Internal Server Error 服务器发生不可预期的错误,导致无法完成客户端的请求。
  • 503 Service Unavailable 服务器当前不能够处理客户端的请求,在一段时间之后,服务器可能会恢复正常。

下面是一个HTTP响应的例子:

HTTP/1.1 200 OK

Server:Apache Tomcat/5.0.12
Date:Mon,6Oct2003 13:23:42 GMT
Content-Length:112

<html>...

条件 GET

HTTP 条件 GET 是 HTTP 协议为了减少不必要的带宽浪费,提出的一种方案。详见 RFC2616

  1. HTTP 条件 GET 使用的时机?

    客户端之前已经访问过某网站,并打算再次访问该网站。

  2. HTTP 条件 GET 使用的方法?

    客户端向服务器发送一个包询问是否在上一次访问网站的时间后是否更改了页面,如果服务器没有更新,显然不需要把整个网页传给客户端,客户端只要使用本地缓存即可,如果服务器对照客户端给出的时间已经更新了客户端请求的网页,则发送这个更新了的网页给用户。

    下面是一个具体的发送接受报文示例:

    客户端发送请求:

    GET / HTTP/1.1  
    Host: www.sina.com.cn:80  
    If-Modified-Since:Thu, 4 Feb 2010 20:39:13 GMT  
    Connection: Close  

    第一次请求时,服务器端返回请求数据,之后的请求,服务器根据请求中的 If-Modified-Since 字段判断响应文件没有更新,如果没有更新,服务器返回一个 304 Not Modified响应,告诉浏览器请求的资源在浏览器上没有更新,可以使用已缓存的上次获取的文件。

    HTTP/1.0 304 Not Modified  
    Date: Thu, 04 Feb 2010 12:38:41 GMT  
    Content-Type: text/html  
    Expires: Thu, 04 Feb 2010 12:39:41 GMT  
    Last-Modified: Thu, 04 Feb 2010 12:29:04 GMT  
    Age: 28  
    X-Cache: HIT from sy32-21.sina.com.cn  
    Connection: close 

    如果服务器端资源已经更新的话,就返回正常的响应。

持久连接

我们知道 HTTP 协议采用“请求-应答”模式,当使用普通模式,即非 Keep-Alive 模式时,每个请求/应答客户和服务器都要新建一个连接,完成之后立即断开连接(HTTP 协议为无连接的协议);当使用 Keep-Alive 模式(又称持久连接、连接重用)时,Keep-Alive 功能使客户端到服务器端的连接持续有效,当出现对服务器的后继请求时,Keep-Alive 功能避免了建立或者重新建立连接。

在 HTTP 1.0 版本中,并没有官方的标准来规定 Keep-Alive 如何工作,因此实际上它是被附加到 HTTP 1.0协议上,如果客户端浏览器支持 Keep-Alive ,那么就在HTTP请求头中添加一个字段 Connection: Keep-Alive,当服务器收到附带有 Connection: Keep-Alive 的请求时,它也会在响应头中添加一个同样的字段来使用 Keep-Alive 。这样一来,客户端和服务器之间的HTTP连接就会被保持,不会断开(超过 Keep-Alive 规定的时间,意外断电等情况除外),当客户端发送另外一个请求时,就使用这条已经建立的连接。

在 HTTP 1.1 版本中,默认情况下所有连接都被保持,如果加入 “Connection: close” 才关闭。目前大部分浏览器都使用 HTTP 1.1 协议,也就是说默认都会发起 Keep-Alive 的连接请求了,所以是否能完成一个完整的 Keep-Alive 连接就看服务器设置情况。

由于 HTTP 1.0 没有官方的 Keep-Alive 规范,并且也已经基本被淘汰,以下讨论均是针对 HTTP 1.1 标准中的 Keep-Alive 展开的。

注意:

  • HTTP Keep-Alive 简单说就是保持当前的TCP连接,避免了重新建立连接。

  • HTTP 长连接不可能一直保持,例如 Keep-Alive: timeout=5, max=100,表示这个TCP通道可以保持5秒,max=100,表示这个长连接最多接收100次请求就断开。

  • HTTP 是一个无状态协议,这意味着每个请求都是独立的,Keep-Alive 没能改变这个结果。另外,Keep-Alive也不能保证客户端和服务器之间的连接一定是活跃的,在 HTTP1.1 版本中也如此。唯一能保证的就是当连接被关闭时你能得到一个通知,所以不应该让程序依赖于 Keep-Alive 的保持连接特性,否则会有意想不到的后果。

  • 使用长连接之后,客户端、服务端怎么知道本次传输结束呢?两部分:1. 判断传输数据是否达到了Content-Length 指示的大小;2. 动态生成的文件没有 Content-Length ,它是分块传输(chunked),这时候就要根据 chunked 编码来判断,chunked 编码的数据在最后有一个空 chunked 块,表明本次传输数据结束,详见这里。什么是 chunked 分块传输呢?下面我们就来介绍一下。

Transfer-Encoding

Transfer-Encoding 是一个用来标示 HTTP 报文传输格式的头部值。尽管这个取值理论上可以有很多,但是当前的 HTTP 规范里实际上只定义了一种传输取值——chunked。

如果一个HTTP消息(请求消息或应答消息)的Transfer-Encoding消息头的值为chunked,那么,消息体由数量未定的块组成,并以最后一个大小为0的块为结束。

每一个非空的块都以该块包含数据的字节数(字节数以十六进制表示)开始,跟随一个CRLF (回车及换行),然后是数据本身,最后块CRLF结束。在一些实现中,块大小和CRLF之间填充有白空格(0x20)。

最后一块是单行,由块大小(0),一些可选的填充白空格,以及CRLF。最后一块不再包含任何数据,但是可以发送可选的尾部,包括消息头字段。消息最后以CRLF结尾。

一个示例响应如下:

HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked

25
This is the data in the first chunk

1A
and this is the second one
0

注意:

  • chunked 和 multipart 两个名词在意义上有类似的地方,不过在 HTTP 协议当中这两个概念则不是一个类别的。multipart 是一种 Content-Type,标示 HTTP 报文内容的类型,而 chunked 是一种传输格式,标示报头将以何种方式进行传输。
  • chunked 传输不能事先知道内容的长度,只能靠最后的空 chunk 块来判断,因此对于下载请求来说,是没有办法实现进度的。在浏览器和下载工具中,偶尔我们也会看到有些文件是看不到下载进度的,即采用 chunked 方式进行下载。
  • chunked 的优势在于,服务器端可以边生成内容边发送,无需事先生成全部的内容。HTTP/2 不支持 Transfer-Encoding: chunked,因为 HTTP/2 有自己的 streaming 传输方式(Source:MDN - Transfer-Encoding)。

HTTP Pipelining(HTTP 管线化)

默认情况下 HTTP 协议中每个传输层连接只能承载一个 HTTP 请求和响应,浏览器会在收到上一个请求的响应之后,再发送下一个请求。在使用持久连接的情况下,某个连接上消息的传递类似于请求1 -> 响应1 -> 请求2 -> 响应2 -> 请求3 -> 响应3

HTTP Pipelining(管线化)是将多个 HTTP 请求整批提交的技术,在传送过程中不需等待服务端的回应。使用 HTTP Pipelining 技术之后,某个连接上的消息变成了类似这样请求1 -> 请求2 -> 请求3 -> 响应1 -> 响应2 -> 响应3

注意下面几点:

  • 管线化机制通过持久连接(persistent connection)完成,仅 HTTP/1.1 支持此技术(HTTP/1.0不支持)
  • 只有 GET 和 HEAD 请求可以进行管线化,而 POST 则有所限制
  • 初次创建连接时不应启动管线机制,因为对方(服务器)不一定支持 HTTP/1.1 版本的协议
  • 管线化不会影响响应到来的顺序,如上面的例子所示,响应返回的顺序并未改变
  • HTTP /1.1 要求服务器端支持管线化,但并不要求服务器端也对响应进行管线化处理,只是要求对于管线化的请求不失败即可
  • 由于上面提到的服务器端问题,开启管线化很可能并不会带来大幅度的性能提升,而且很多服务器端和代理程序对管线化的支持并不好,因此现代浏览器如 Chrome 和 Firefox 默认并未开启管线化支持

更多关于 HTTP Pipelining 的知识可以参考这里

会话跟踪

  1. 什么是会话?

    客户端打开与服务器的连接发出请求到服务器响应客户端请求的全过程称之为会话。

  2. 什么是会话跟踪?

    会话跟踪指的是对同一个用户对服务器的连续的请求和接受响应的监视。

  3. 为什么需要会话跟踪?

    浏览器与服务器之间的通信是通过HTTP协议进行通信的,而HTTP协议是”无状态”的协议,它不能保存客户的信息,即一次响应完成之后连接就断开了,下一次的请求需要重新连接,这样就需要判断是否是同一个用户,所以才有会话跟踪技术来实现这种要求。

  4. 会话跟踪常用的方法:

    1. URL 重写

      URL(统一资源定位符)是Web上特定页面的地址,URL重写的技术就是在URL结尾添加一个附加数据以标识该会话,把会话ID通过URL的信息传递过去,以便在服务器端进行识别不同的用户。

    2. 隐藏表单域

      将会话ID添加到HTML表单元素中提交到服务器,此表单元素并不在客户端显示

    3. Cookie

      Cookie 是Web 服务器发送给客户端的一小段信息,客户端请求时可以读取该信息发送到服务器端,进而进行用户的识别。对于客户端的每次请求,服务器都会将 Cookie 发送到客户端,在客户端可以进行保存,以便下次使用。

      客户端可以采用两种方式来保存这个 Cookie 对象,一种方式是保存在客户端内存中,称为临时 Cookie,浏览器关闭后这个 Cookie 对象将消失。另外一种方式是保存在客户机的磁盘上,称为永久 Cookie。以后客户端只要访问该网站,就会将这个 Cookie 再次发送到服务器上,前提是这个 Cookie 在有效期内,这样就实现了对客户的跟踪。

      Cookie 是可以被客户端禁用的。

    4. Session:

      每一个用户都有一个不同的 session,各个用户之间是不能共享的,是每个用户所独享的,在 session 中可以存放信息。

      在服务器端会创建一个 session 对象,产生一个 sessionID 来标识这个 session 对象,然后将这个 sessionID 放入到 Cookie 中发送到客户端,下一次访问时,sessionID 会发送到服务器,在服务器端进行识别不同的用户。

      Session 的实现依赖于 Cookie,如果 Cookie 被禁用,那么 session 也将失效。

跨站攻击

  • CSRF(Cross-site request forgery,跨站请求伪造)

    CSRF(XSRF) 顾名思义,是伪造请求,冒充用户在站内的正常操作。

    例如,一论坛网站的发贴是通过 GET 请求访问,点击发贴之后 JS 把发贴内容拼接成目标 URL 并访问:

    http://example.com/bbs/create_post.php?title=标题&content=内容

    那么,我们只需要在论坛中发一帖,包含一链接:

    http://example.com/bbs/create_post.php?title=我是脑残&content=哈哈

    只要有用户点击了这个链接,那么他们的帐户就会在不知情的情况下发布了这一帖子。可能这只是个恶作剧,但是既然发贴的请求可以伪造,那么删帖、转帐、改密码、发邮件全都可以伪造。

    如何防范 CSRF 攻击?可以注意以下几点:

    • 关键操作只接受 POST 请求

    • 验证码

      CSRF 攻击的过程,往往是在用户不知情的情况下构造网络请求。所以如果使用验证码,那么每次操作都需要用户进行互动,从而简单有效的防御了CSRF攻击。

      但是如果你在一个网站作出任何举动都要输入验证码会严重影响用户体验,所以验证码一般只出现在特殊操作里面,或者在注册时候使用。

    • 检测 Referer

      常见的互联网页面与页面之间是存在联系的,比如你在 www.baidu.com 应该是找不到通往www.google.com 的链接的,再比如你在论坛留言,那么不管你留言后重定向到哪里去了,之前的那个网址一定会包含留言的输入框,这个之前的网址就会保留在新页面头文件的 Referer

    通过检查 Referer 的值,我们就可以判断这个请求是合法的还是非法的,但是问题出在服务器不是任何时候都能接受到 Referer 的值,所以 Referer Check 一般用于监控 CSRF 攻击的发生,而不用来抵御攻击。

    • Token

      目前主流的做法是使用 Token 抵御 CSRF 攻击。下面通过分析 CSRF 攻击来理解为什么 Token 能够有效

      CSRF 攻击要成功的条件在于攻击者能够预测所有的参数从而构造出合法的请求。所以根据不可预测性原则,我们可以对参数进行加密从而防止 CSRF 攻击。

      另一个更通用的做法是保持原有参数不变,另外添加一个参数 Token,其值是随机的。这样攻击者因为不知道 Token 而无法构造出合法的请求进行攻击。

    Token 使用原则

    • Token 要足够随机————只有这样才算不可预测
    • Token 是一次性的,即每次请求成功后要更新Token————这样可以增加攻击难度,增加预测难度
    • Token 要注意保密性————敏感操作使用 post,防止 Token 出现在 URL 中

    注意:过滤用户输入的内容不能阻挡 csrf,我们需要做的是过滤请求的来源

  • XSS(Cross Site Scripting,跨站脚本攻击)

    XSS 全称“跨站脚本”,是注入攻击的一种。其特点是不对服务器端造成任何伤害,而是通过一些正常的站内交互途径,例如发布评论,提交含有 JavaScript 的内容文本。这时服务器端如果没有过滤或转义掉这些脚本,作为内容发布到了页面上,其他用户访问这个页面的时候就会运行这些脚本。

    运行预期之外的脚本带来的后果有很多中,可能只是简单的恶作剧——一个关不掉的窗口:

    while (true) {
        alert("你关不掉我~");
    }

    也可以是盗号或者其他未授权的操作。

    XSS 是实现 CSRF 的诸多途径中的一条,但绝对不是唯一的一条。一般习惯上把通过 XSS 来实现的 CSRF 称为 XSRF。

    如何防御 XSS 攻击?

    理论上,所有可输入的地方没有对输入数据进行处理的话,都会存在 XSS 漏洞,漏洞的危害取决于攻击代码的威力,攻击代码也不局限于 script。防御 XSS 攻击最简单直接的方法,就是过滤用户的输入。

    如果不需要用户输入 HTML,可以直接对用户的输入进行 HTML escape 。下面一小段脚本:

    <script>window.location.href=”http://www.baidu.com”;</script>

    经过 escape 之后就成了:

    &lt;script&gt;window.location.href=&quot;http://www.baidu.com&quot;&lt;/script&gt;

    它现在会像普通文本一样显示出来,变得无毒无害,不能执行了。

    当我们需要用户输入 HTML 的时候,需要对用户输入的内容做更加小心细致的处理。仅仅粗暴地去掉 script 标签是没有用的,任何一个合法 HTML 标签都可以添加 onclick 一类的事件属性来执行 JavaScript。更好的方法可能是,将用户的输入使用 HTML 解析库进行解析,获取其中的数据。然后根据用户原有的标签属性,重新构建 HTML 元素树。构建的过程中,所有的标签、属性都只从白名单中拿取。

HTTP

HTTPS 基本过程

HTTPS 即 HTTP over TLS,是一种在加密信道进行 HTTP 内容传输的协议。

TLS 的早期版本叫做 SSL。SSL 的 1.0, 2.0, 3.0 版本均已经被废弃,出于安全问题考虑广大浏览器也不再对老旧的 SSL 版本进行支持了,因此这里我们就统一使用 TLS 名称了。

TLS 的基本过程如下(取自 what-happens-when-zh_CN):

  • 客户端发送一个 ClientHello 消息到服务器端,消息中同时包含了它的 Transport Layer Security (TLS) 版本,可用的加密算法和压缩算法。
  • 服务器端向客户端返回一个 ServerHello 消息,消息中包含了服务器端的 TLS 版本,服务器所选择的加密和压缩算法,以及数字证书认证机构(Certificate Authority,缩写 CA)签发的服务器公开证书,证书中包含了公钥。客户端会使用这个公钥加密接下来的握手过程,直到协商生成一个新的对称密钥。证书中还包含了该证书所应用的域名范围(Common Name,简称 CN),用于客户端验证身份。
  • 客户端根据自己的信任 CA 列表,验证服务器端的证书是否可信。如果认为可信(具体的验证过程在下一节讲解),客户端会生成一串伪随机数,使用服务器的公钥加密它。这串随机数会被用于生成新的对称密钥
  • 服务器端使用自己的私钥解密上面提到的随机数,然后使用这串随机数生成自己的对称主密钥
  • 客户端发送一个 Finished 消息给服务器端,使用对称密钥加密这次通讯的一个散列值
  • 服务器端生成自己的 hash 值,然后解密客户端发送来的信息,检查这两个值是否对应。如果对应,就向客户端发送一个 Finished 消息,也使用协商好的对称密钥加密
  • 从现在开始,接下来整个 TLS 会话都使用对称秘钥进行加密,传输应用层(HTTP)内容

从上面的过程可以看到,TLS 的完整过程需要三个算法(协议),密钥交互算法,对称加密算法,和消息认证算法(TLS 的传输会使用 MAC(message authentication code) 进行完整性检查)。

我们以 Github 网站使用的 TLS 为例,使用浏览器可以看到它使用的加密为 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256。其中密钥交互算法是 ECDHE_RSA,对称加密算法是 AES_128_GCM,消息认证(MAC)算法为 SHA256

TLS 证书机制

HTTPS 过程中很重要的一个步骤,是服务器需要有 CA 颁发的证书,客户端根据自己的信任 CA 列表验证服务器的身份。现代浏览器中,证书验证的过程依赖于证书信任链。

所谓证书信任链,即一个证书要依靠上一级证书来证明自己是可信的,最顶层的证书被称为根证书,拥有根证书的机构被称为根 CA。

还是以 Github 为例,在浏览器中我们可以看到它的证书信任链如下:

DigiCert High Assurance EV Root CA -> DigiCert SHA2 Extended Validation Server CA -> Github.com

从上到下即 Root CA -> 二级 CA -> 网站。

前面提到,证书当中包括 CN(Common Name),浏览器在验证证书的同时,也会验证 CN 的正确性。即不光需要验证“这是一个合法的证书”,还需要验证“这是一个用于 Github.com 的证书”。

既然所有的信任,最终要落到根 CA 上,根证书本身又是怎么获得的呢?答案也很简单,根证书一般是操作系统自带的。不管是桌面系统 Windows,macOS 还是移动端系统 Android, iOS 都会内置一系列根证书。随着操作系统本身的升级,根证书也会随着升级进行更新。

对浏览器而已,浏览器当然也有选择信任某个根证书的权利。Chrome 浏览器一般是跟随系统根证书信任的。Firefox 浏览器通常是使用自带的一套证书信任机制,不受系统证书的影响。

在使用 curl 等工具时,我们还可以自行选择证书进行信任。

有权威的信任,最终都要落到一个单点信任,不管是 Root CA,还是微软,苹果,谷歌等操作系统厂商。

中间人攻击

HTTPS 的过程并不是密不透风的,HTTPS 有若干漏洞,给中间人攻击(Man In The Middle Attack,简称 MITM)提供了可能。

所谓中间人攻击,指攻击者与通讯的两端分别建立独立的联系,并交换其所收到的数据,使通讯的两端认为他们正在通过一个私密的连接与对方直接对话,但事实上整个会话都被攻击者完全控制。在中间人攻击中,攻击者可以拦截通讯双方的通话并插入新的内容。

SSL 剥离

SSL 剥离即阻止用户使用 HTTPS 访问网站。由于并不是所有网站都只支持 HTTPS,大部分网站会同时支持 HTTP 和 HTTPS 两种协议。用户在访问网站时,也可能会在地址栏中输入 http:// 的地址,第一次的访问完全是明文的,这就给了攻击者可乘之机。通过攻击 DNS 响应,攻击者可以将自己变成中间人。

DNS 作为基于 UDP 的协议是相当不安全的,为了保证 DNS 的安全可以使用 DNS over TCP 等机制,这里不赘述了。

HSTS

为了防止上面说的这种情况,一种叫做 HSTS 的技术被引入了。HSTS(HTTP Strict Transport Security)是用于强制浏览器使用 HTTPS 访问网站的一种机制。它的基本机制是在服务器返回的响应中,加上一个特殊的头部,指示浏览器对于此网站,强制使用 HTTPS 进行访问:

1
Strict-Transport-Security: max-age=31536000; includeSubdomains; preload

可以看到如果这个过期时间非常长,就是导致在很长一段时间内,浏览器都会强制使用 HTTPS 访问该网站。

HSTS 有一个很明显的缺点,是需要等待第一个服务器的影响中的头部才能生效,但如果第一次访问该网站就被攻击呢?为了解决这个问题,浏览器中会带上一些网站的域名,被称为 HSTS preload list。对于在这个 list 的网站来说,直接强制使用 HTTPS。

伪造证书攻击

HSTS 只解决了 SSL 剥离的问题,然而即使在全程使用 HTTPS 的情况下,我们仍然有可能被监听。

假设我们想访问 www.google.com,但我们的 DNS 服务器被攻击了,指向的 IP 地址并非 Google 的服务器,而是攻击者的 IP。当攻击者的服务器也有合法的证书的时候,我们的浏览器就会认为对方是 Google 服务器,从而信任对方。这样,攻击者便可以监听我们和谷歌之前的所有通信了。

可以看到攻击者有两步需要操作,第一步是需要攻击 DNS 服务器。第二步是攻击者自己的证书需要被用户信任,这一步对于用户来说是很难控制的,需要证书颁发机构能够控制自己不滥发证书。

2015 年 Google 称发现赛门铁克旗下的 Thawte 未经同意签发了众多域名的数千个证书,其中包括 Google 旗下的域名和不存在的域名。当年 12 月,Google 发布公告称 Chrome、Android 及其他 Google 产品将不再信任赛门铁克旗下的”Class 3 Public Primary CA”根证书。

2016 年 Mozilla 发现沃通 CA 存在严重的信任问题,例如偷签 github.com 的证书,故意倒填证书日期绕过浏览器对 SHA-1 证书的限制等,将停止信任 WoSign 和 StartCom 签发的新证书。

HPKP

HPKP 技术是为了解决伪造证书攻击而诞生的。

HPKP(Public Key Pinning Extension for HTTP)在 HSTS 上更进一步,HPKP 直接在返回头中存储服务器的公钥指纹信息,一旦发现指纹和实际接受到的公钥有差异,浏览器就可以认为正在被攻击:

1
Public-Key-Pins: pin-sha256="base64=="; max-age=expireTime [; includeSubDomains][; report-uri="reportURI"]

和 HSTS 类似,HPKP 也依赖于服务器的头部返回,不能解决第一次访问的问题,浏览器本身也会内置一些 HPKP 列表。

HPKP 技术仍然不能阻止第一次访问的攻击问题,部署和配置 HPKP 相当繁琐,一旦网站配置错误,就会导致网站证书验证失败,且在过期时间内无法有效恢复。HPKP 的机制也引来了一些安全性问题。Chrome 67 中废除了对 HPKP 的支持,在 Chrome 72 中 HPKP 被彻底移除。

IP

IP 协议简介

IP 协议位于 TCP/IP 协议的第三层——网络层。与传输层协议相比,网络层的责任是提供点到点(hop by hop)的服务,而传输层(TCP/UDP)则提供端到端(end to end)的服务。

IP 地址的分类

A类地址

B类地址

C类地址

D 类地址

广播与多播

广播和多播仅用于UDP(TCP是面向连接的)。

  • 广播

    一共有四种广播地址:

    1. 受限的广播

      受限的广播地址为255.255.255.255。该地址用于主机配置过程中IP数据报的目的地址,在任何情况下,router不转发目的地址为255.255.255.255的数据报,这样的数据报仅出现在本地网络中。

    2. 指向网络的广播

      指向网络的广播地址是主机号为全1的地址。A类网络广播地址为netid.255.255.255,其中netid为A类网络的网络号。

      一个router必须转发指向网络的广播,但它也必须有一个不进行转发的选择。

    3. 指向子网的广播

      指向子网的广播地址为主机号为全1且有特定子网号的地址。作为子网直接广播地址的IP地址需要了解子网的掩码。例如,router收到128.1.2.255的数据报,当B类网路128.1的子网掩码为255.255.255.0时,该地址就是指向子网的广播地址;但是如果子网掩码为255.255.254.0,该地址就不是指向子网的广播地址。

    4. 指向所有子网的广播

      指向所有子网的广播也需要了解目的网络的子网掩码,以便与指向网络的广播地址区分开来。指向所有子网的广播地址的子网号和主机号为全1.例如,如果子网掩码为255.255.255.0,那么128.1.255.255就是一个指向所有子网的广播地址。

      当前的看法是这种广播是陈旧过时的,更好的方式是使用多播而不是对所有子网的广播。

    广播示例:

    1
    2
    3
    4
    5
    6
    7
    PING 192.168.0.255 (192.168.0.255): 56 data bytes
    64 bytes from 192.168.0.107: icmp_seq=0 ttl=64 time=0.199 ms
    64 bytes from 192.168.0.106: icmp_seq=0 ttl=64 time=45.357 ms
    64 bytes from 192.168.0.107: icmp_seq=1 ttl=64 time=0.203 ms
    64 bytes from 192.168.0.106: icmp_seq=1 ttl=64 time=269.475 ms
    64 bytes from 192.168.0.107: icmp_seq=2 ttl=64 time=0.102 ms
    64 bytes from 192.168.0.106: icmp_seq=2 ttl=64 time=189.881 ms

    可以看到的确收到了来自两个主机的答复,其中 192.168.0.107 是本机地址。

  • 多播

    多播又叫组播,使用D类地址,D类地址分配的28bit均用作多播组号而不再表示其他。

    多播组地址包括1110的最高4bit和多播组号。它们通常可以表示为点分十进制数,范围从224.0.0.0到239.255.255.255。

    多播的出现减少了对应用不感兴趣主机的处理负荷。

    多播的特点:

    • 允许一个或多个发送者(组播源)发送单一的数据包到多个接收者(一次的,同时的)的网络技术
    • 可以大大的节省网络带宽,因为无论有多少个目标地址,在整个网络的任何一条链路上只传送单一的数据包
    • 多播技术的核心就是针对如何节约网络资源的前提下保证服务质量。

    多播示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    PING 224.0.0.1 (224.0.0.1): 56 data bytes
    64 bytes from 192.168.0.107: icmp_seq=0 ttl=64 time=0.081 ms
    64 bytes from 192.168.0.106: icmp_seq=0 ttl=64 time=123.081 ms
    64 bytes from 192.168.0.107: icmp_seq=1 ttl=64 time=0.122 ms
    64 bytes from 192.168.0.106: icmp_seq=1 ttl=64 time=67.312 ms
    64 bytes from 192.168.0.107: icmp_seq=2 ttl=64 time=0.132 ms
    64 bytes from 192.168.0.106: icmp_seq=2 ttl=64 time=447.073 ms
    64 bytes from 192.168.0.107: icmp_seq=3 ttl=64 time=0.132 ms
    64 bytes from 192.168.0.106: icmp_seq=3 ttl=64 time=188.800 ms

BGP

  • 边界网关协议(BGP)是运行于 TCP 上的一种自治系统的路由协议

  • BGP 是唯一一个用来处理像因特网大小的网络的协议,也是唯一能够妥善处理好不相关路由域间的多路连接的协议

  • BGP是一种外部网关协议(Exterior Gateway Protocol,EGP),与OSPF、RIP等内部网关协议(Interior Gateway Protocol,IGP)不同,BGP不在于发现和计算路由,而在于控制路由的传播和选择最佳路由

  • BGP使用TCP作为其传输层协议(端口号179),提高了协议的可靠性

  • BGP既不是纯粹的矢量距离协议,也不是纯粹的链路状态协议

  • BGP支持CIDR(Classless Inter-Domain Routing,无类别域间路由)

  • 路由更新时,BGP只发送更新的路由,大大减少了BGP传播路由所占用的带宽,适用于在Internet上传播大量的路由信息

  • BGP路由通过携带AS路径信息彻底解决路由环路问题

  • BGP提供了丰富的路由策略,能够对路由实现灵活的过滤和选择

  • BGP易于扩展,能够适应网络新的发展

TCP

TCP 的特性

  • TCP 提供一种面向连接的、可靠的字节流服务
  • 在一个 TCP 连接中,仅有两方进行彼此通信。广播和多播不能用于 TCP
  • TCP 使用校验和,确认和重传机制来保证可靠传输
  • TCP 给数据分节进行排序,并使用累积确认保证数据的顺序不变和非重复
  • TCP 使用滑动窗口机制来实现流量控制,通过动态改变窗口的大小进行拥塞控制

注意:TCP 并不能保证数据一定会被对方接收到,因为这是不可能的。TCP 能够做到的是,如果有可能,就把数据递送到接收方,否则就(通过放弃重传并且中断连接这一手段)通知用户。因此准确说 TCP 也不是 100% 可靠的协议,它所能提供的是数据的可靠递送或故障的可靠通知。

三次握手与四次挥手

所谓三次握手(Three-way Handshake),是指建立一个 TCP 连接时,需要客户端和服务器总共发送3个包。

三次握手的目的是连接服务器指定端口,建立 TCP 连接,并同步连接双方的序列号和确认号,交换 TCP 窗口大小信息。在 socket 编程中,客户端执行 connect() 时。将触发三次握手。

  • 第一次握手(SYN=1, seq=x):

    客户端发送一个 TCP 的 SYN 标志位置1的包,指明客户端打算连接的服务器的端口,以及初始序号 X,保存在包头的序列号(Sequence Number)字段里。

    发送完毕后,客户端进入 SYN_SEND 状态。

  • 第二次握手(SYN=1, ACK=1, seq=y, ACKnum=x+1):

    服务器发回确认包(ACK)应答。即 SYN 标志位和 ACK 标志位均为1。服务器端选择自己 ISN 序列号,放到 Seq 域里,同时将确认序号(Acknowledgement Number)设置为客户的 ISN 加1,即X+1。
    发送完毕后,服务器端进入 SYN_RCVD 状态。

  • 第三次握手(ACK=1,ACKnum=y+1)

    客户端再次发送确认包(ACK),SYN 标志位为0,ACK 标志位为1,并且把服务器发来 ACK 的序号字段+1,放在确定字段中发送给对方,并且在数据段放写ISN的+1

    发送完毕后,客户端进入 ESTABLISHED 状态,当服务器端接收到这个包时,也进入 ESTABLISHED 状态,TCP 握手结束。

三次握手的过程的示意图如下:

three-way-handshake

TCP 的连接的拆除需要发送四个包,因此称为四次挥手(Four-way handshake),也叫做改进的三次握手。客户端或服务器均可主动发起挥手动作,在 socket 编程中,任何一方执行 close() 操作即可产生挥手操作。

  • 第一次挥手(FIN=1,seq=x)

    假设客户端想要关闭连接,客户端发送一个 FIN 标志位置为1的包,表示自己已经没有数据可以发送了,但是仍然可以接受数据。

    发送完毕后,客户端进入 FIN_WAIT_1 状态。

  • 第二次挥手(ACK=1,ACKnum=x+1)

    服务器端确认客户端的 FIN 包,发送一个确认包,表明自己接受到了客户端关闭连接的请求,但还没有准备好关闭连接。

    发送完毕后,服务器端进入 CLOSE_WAIT 状态,客户端接收到这个确认包之后,进入 FIN_WAIT_2 状态,等待服务器端关闭连接。

  • 第三次挥手(FIN=1,seq=y)

    服务器端准备好关闭连接时,向客户端发送结束连接请求,FIN 置为1。

    发送完毕后,服务器端进入 LAST_ACK 状态,等待来自客户端的最后一个ACK。

  • 第四次挥手(ACK=1,ACKnum=y+1)

    客户端接收到来自服务器端的关闭请求,发送一个确认包,并进入 TIME_WAIT状态,等待可能出现的要求重传的 ACK 包。

    服务器端接收到这个确认包之后,关闭连接,进入 CLOSED 状态。

    客户端等待了某个固定时间(两个最大段生命周期,2MSL,2 Maximum Segment Lifetime)之后,没有收到服务器端的 ACK ,认为服务器端已经正常关闭连接,于是自己也关闭连接,进入 CLOSED 状态。

四次挥手的示意图如下:

four-way-handshake

SYN攻击

  • 什么是 SYN 攻击(SYN Flood)?

    在三次握手过程中,服务器发送 SYN-ACK 之后,收到客户端的 ACK 之前的 TCP 连接称为半连接(half-open connect)。此时服务器处于 SYN_RCVD 状态。当收到 ACK 后,服务器才能转入 ESTABLISHED 状态.

    SYN 攻击指的是,攻击客户端在短时间内伪造大量不存在的IP地址,向服务器不断地发送SYN包,服务器回复确认包,并等待客户的确认。由于源地址是不存在的,服务器需要不断的重发直至超时,这些伪造的SYN包将长时间占用未连接队列,正常的SYN请求被丢弃,导致目标系统运行缓慢,严重者会引起网络堵塞甚至系统瘫痪。

    SYN 攻击是一种典型的 DoS/DDoS 攻击。

  • 如何检测 SYN 攻击?

    检测 SYN 攻击非常的方便,当你在服务器上看到大量的半连接状态时,特别是源IP地址是随机的,基本上可以断定这是一次SYN攻击。在 Linux/Unix 上可以使用系统自带的 netstats 命令来检测 SYN 攻击。

  • 如何防御 SYN 攻击?

    SYN攻击不能完全被阻止,除非将TCP协议重新设计。我们所做的是尽可能的减轻SYN攻击的危害,常见的防御 SYN 攻击的方法有如下几种:

    • 缩短超时(SYN Timeout)时间
    • 增加最大半连接数
    • 过滤网关防护
    • SYN cookies技术

TCP KeepAlive

TCP 的连接,实际上是一种纯软件层面的概念,在物理层面并没有“连接”这种概念。TCP 通信双方建立交互的连接,但是并不是一直存在数据交互,有些连接会在数据交互完毕后,主动释放连接,而有些不会。在长时间无数据交互的时间段内,交互双方都有可能出现掉电、死机、异常重启等各种意外,当这些意外发生之后,这些 TCP 连接并未来得及正常释放,在软件层面上,连接的另一方并不知道对端的情况,它会一直维护这个连接,长时间的积累会导致非常多的半打开连接,造成端系统资源的消耗和浪费,为了解决这个问题,在传输层可以利用 TCP 的 KeepAlive 机制实现来实现。主流的操作系统基本都在内核里支持了这个特性。

TCP KeepAlive 的基本原理是,隔一段时间给连接对端发送一个探测包,如果收到对方回应的 ACK,则认为连接还是存活的,在超过一定重试次数之后还是没有收到对方的回应,则丢弃该 TCP 连接。

TCP-Keepalive-HOWTO 有对 TCP KeepAlive 特性的详细介绍,有兴趣的同学可以参考。这里主要说一下,TCP KeepAlive 的局限。首先 TCP KeepAlive 监测的方式是发送一个 probe 包,会给网络带来额外的流量,另外 TCP KeepAlive 只能在内核层级监测连接的存活与否,而连接的存活不一定代表服务的可用。例如当一个服务器 CPU 进程服务器占用达到 100%,已经卡死不能响应请求了,此时 TCP KeepAlive 依然会认为连接是存活的。因此 TCP KeepAlive 对于应用层程序的价值是相对较小的。需要做连接保活的应用层程序,例如 QQ,往往会在应用层实现自己的心跳功能。

UDP

UDP 简介

UDP 是一个简单的传输层协议。和 TCP 相比,UDP 有下面几个显著特性:

  • UDP 缺乏可靠性。UDP 本身不提供确认,序列号,超时重传等机制。UDP 数据报可能在网络中被复制,被重新排序。即 UDP 不保证数据报会到达其最终目的地,也不保证各个数据报的先后顺序,也不保证每个数据报只到达一次
  • UDP 数据报是有长度的。每个 UDP 数据报都有长度,如果一个数据报正确地到达目的地,那么该数据报的长度将随数据一起传递给接收方。而 TCP 是一个字节流协议,没有任何(协议上的)记录边界。
  • UDP 是无连接的。UDP 客户和服务器之前不必存在长期的关系。UDP 发送数据报之前也不需要经过握手创建连接的过程。
  • UDP 支持多播和广播。

Socket

Socket 基本概念

Socket 是对 TCP/IP 协议族的一种封装,是应用层与TCP/IP协议族通信的中间软件抽象层。从设计模式的角度看来,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

Socket 还可以认为是一种网络间不同计算机上的进程通信的一种方法,利用三元组(ip地址,协议,端口)就可以唯一标识网络中的进程,网络中的进程通信可以利用这个标志与其它进程进行交互。

Socket 起源于 Unix ,Unix/Linux 基本哲学之一就是“一切皆文件”,都可以用“打开(open) –> 读写(write/read) –> 关闭(close)”模式来进行操作。因此 Socket 也被处理为一种特殊的文件。

写一个简易的 WebServer

一个简易的 Server 的流程如下:

  • 1.建立连接,接受一个客户端连接。
  • 2.接受请求,从网络中读取一条 HTTP 请求报文。
  • 3.处理请求,访问资源。
  • 4.构建响应,创建带有 header 的 HTTP 响应报文。
  • 5.发送响应,传给客户端。

省略流程 3,大体的程序与调用的函数逻辑如下:

  • socket() 创建套接字
  • bind() 分配套接字地址
  • listen() 等待连接请求
  • accept() 允许连接请求
  • read()/write() 数据交换
  • close() 关闭连接

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string>
#include <cstring>
#include <iostream>

using namespace std;

const int port = 9090;
const int buffer_size = 1<<20;
const int method_size = 1<<10;
const int filename_size = 1<<10;
const int common_buffer_size = 1<<10;

void handleError(const string &message);
void requestHandling(int *sock);
void sendError(int *sock);
void sendData(int *sock, char *filename);
void sendHTML(int *sock, char *filename);
void sendJPG(int *sock, char *filename);

int main()
{
int server_sock;
int client_sock;

struct sockaddr_in server_address;
struct sockaddr_in client_address;

socklen_t client_address_size;

server_sock = socket(PF_INET, SOCK_STREAM, 0);

if (server_sock == -1)
{
handleError("socket error");
}

memset(&server_address,0,sizeof(server_address));
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = htonl(INADDR_ANY);
server_address.sin_port = htons(port);

if(bind(server_sock,(struct sockaddr*)&server_address, sizeof(server_address)) == -1){
handleError("bind error");
}

if(listen(server_sock, 5) == -1) {
handleError("listen error");
}

while(true) {
client_address_size = sizeof(client_address);
client_sock = accept(server_sock, (struct sockaddr*) &client_address, &client_address_size);

if (client_sock == -1) {
handleError("accept error");
}
requestHandling(&client_sock);
}

//system("open http://127.0.0.1:9090/index.html");
close(server_sock);

return 0;
}

void requestHandling(int *sock){
int client_sock = *sock;
char buffer[buffer_size];
char method[method_size];
char filename[filename_size];

read(client_sock, buffer, sizeof(buffer)-1);

if(!strstr(buffer, "HTTP/")) {
sendError(sock);
close(client_sock);
return;
}

strcpy(method, strtok(buffer," /"));
strcpy(filename, strtok(NULL, " /"));

if(0 != strcmp(method, "GET")) {
sendError(sock);
close(client_sock);
return;
}

sendData(sock, filename);
}

void sendData(int *sock, char *filename) {
int client_sock = *sock;
char buffer[common_buffer_size];
char type[common_buffer_size];

strcpy(buffer, filename);

strtok(buffer, ".");
strcpy(type, strtok(NULL, "."));

if(0 == strcmp(type, "html")){
sendHTML(sock, filename);
}else if(0 == strcmp(type, "jpg")){
sendJPG(sock, filename);
}else{
sendError(sock);
close(client_sock);
return ;
}
}

void sendHTML(int *sock, char *filename) {
int client_sock = *sock;
char buffer[buffer_size];
FILE *fp;

char status[] = "HTTP/1.0 200 OK\r\n";
char header[] = "Server: A Simple Web Server\r\nContent-Type: text/html\r\n\r\n";

write(client_sock, status, strlen(status));
write(client_sock, header, strlen(header));

fp = fopen(filename, "r");
if(!fp){
sendError(sock);
close(client_sock);
handleError("failed to open file");
return ;
}

fgets(buffer,sizeof(buffer), fp);
while(!feof(fp)) {
write(client_sock, buffer, strlen(buffer));
fgets(buffer, sizeof(buffer), fp);
}

fclose(fp);
close(client_sock);
}

void sendJPG(int *sock, char *filename) {
int client_sock = *sock;
char buffer[buffer_size];
FILE *fp;
FILE *fw;

char status[] = "HTTP/1.0 200 OK\r\n";
char header[] = "Server: A Simple Web Server\r\nContent-Type: image/jpeg\r\n\r\n";

write(client_sock, status, strlen(status));
write(client_sock, header, strlen(header));

fp = fopen(filename, "rb");
if(NULL == fp){
sendError(sock);
close(client_sock);
handleError("failed to open file");
return ;
}

fw = fdopen(client_sock, "w");
fread(buffer, 1, sizeof(buffer), fp);
while (!feof(fp)){
fwrite(buffer, 1, sizeof(buffer), fw);
fread(buffer, 1, sizeof(buffer), fp);
}

fclose(fw);
fclose(fp);
close(client_sock);
}

void handleError(const string &message) {
cout<<message;
exit(1);
}

void sendError(int *sock){
int client_sock = *sock;

char status[] = "HTTP/1.0 400 Bad Request\r\n";
char header[] = "Server: A Simple Web Server\r\nContent-Type: text/html\r\n\r\n";
char body[] = "<html><head><title>Bad Request</title></head><body><p>400 Bad Request</p></body></html>";

write(client_sock, status, sizeof(status));
write(client_sock, header, sizeof(header));
write(client_sock, body, sizeof(body));
}

1. Basic Operations

a. export

Displays all environment variables. If you want to get details of a specific variable, use echo $VARIABLE_NAME.

1
export

Example:

1
2
3
4
$ export
LANG=en_US.UTF-8
LC_CTYPE=en_US.UTF-8
LESS=-R

b. whatis

whatis shows description for user commands, system calls, library functions, and others in manual pages

1
whatis something

Example:

1
2
$ whatis bash
bash (1) - GNU Bourne-Again SHell

c. whereis

whereis searches for executables, source files, and manual pages using a database built by system automatically.

1
whereis name

Example:

1
2
$ whereis java
/usr/bin/java

d. which

which searches for executables in the directories specified by the environment variable PATH. This command will print the full path of the executable(s).

1
which program_name

Example:

1
2
$ which java
/usr/bin/java

e. clear

Clears content on window.

1.1. File Operations

cat chmod chown cp diff file find gunzip gzcat gzip head
lpq lpr lprm ls more mv rm tail touch

a. cat

It can be used for the following purposes under UNIX or Linux.

  • Display text files on screen
  • Copy text files
  • Combine text files
  • Create new text files
1
2
3
4
cat filename
cat file1 file2
cat file1 file2 > newcombinedfile
cat < file1 > file2 #copy file1 to file2

b. chmod

The chmod command stands for “change mode” and allows you to change the read, write, and execute permissions on your files and folders. For more information on this command check this link.

1
chmod -options filename

c. chown

The chown command stands for “change owner”, and allows you to change the owner of a given file or folder, which can be a user and a group. Basic usage is simple forward first comes the user (owner), and then the group, delimited by a colon.

1
chown -options user:group filename

d. cp

Copies a file from one location to other.

1
cp filename1 filename2

Where filename1 is the source path to the file and filename2 is the destination path to the file.

e. diff

Compares files, and lists their differences.

1
diff filename1 filename2

f. file

Determine file type.

1
file filename

Example:

1
2
$ file index.html
index.html: HTML document, ASCII text

g. find

Find files in directory

1
find directory options pattern

Example:

1
2
$ find . -name README.md
$ find /home/user1 -name '*.png'

h. gunzip

Un-compresses files compressed by gzip.

1
gunzip filename

i. gzcat

Lets you look at gzipped file without actually having to gunzip it.

1
gzcat filename

j. gzip

Compresses files.

1
gzip filename

k. head

Outputs the first 10 lines of file

1
head filename

l. lpq

Check out the printer queue.

1
lpq

Example:

1
2
3
4
$ lpq
Rank Owner Job File(s) Total Size
active adnanad 59 demo 399360 bytes
1st adnanad 60 (stdin) 0 bytes

m. lpr

Print the file.

1
lpr filename

n. lprm

Remove something from the printer queue.

1
lprm jobnumber

o. ls

Lists your files. ls has many options: -l lists files in ‘long format’, which contains the exact size of the file, who owns the file, who has the right to look at it, and when it was last modified. -a lists all files, including hidden files. For more information on this command check this link.

1
ls option

Example:

$ ls -la
rwxr-xr-x   33 adnan  staff    1122 Mar 27 18:44 .
drwxrwxrwx  60 adnan  staff    2040 Mar 21 15:06 ..
-rw-r--r--@  1 adnan  staff   14340 Mar 23 15:05 .DS_Store
-rw-r--r--   1 adnan  staff     157 Mar 25 18:08 .bumpversion.cfg
-rw-r--r--   1 adnan  staff    6515 Mar 25 18:08 .config.ini
-rw-r--r--   1 adnan  staff    5805 Mar 27 18:44 .config.override.ini
drwxr-xr-x  17 adnan  staff     578 Mar 27 23:36 .git
-rwxr-xr-x   1 adnan  staff    2702 Mar 25 18:08 .gitignore

p. more

Shows the first part of a file (move with space and type q to quit).

1
more filename

q. mv

Moves a file from one location to other.

1
mv filename1 filename2

Where filename1 is the source path to the file and filename2 is the destination path to the file.

Also it can be used for rename a file.

1
mv old_name new_name

r. rm

Removes a file. Using this command on a directory gives you an error.
rm: directory: is a directory
To remove a directory you have to pass -r which will remove the content of the directory recursively. Optionally you can use -f flag to force the deletion i.e. without any confirmations etc.

1
rm filename

s. tail

Outputs the last 10 lines of file. Use -f to output appended data as the file grows.

1
tail filename

t. touch

Updates access and modification time stamps of your file. If it doesn’t exists, it’ll be created.

1
touch filename

Example:

1
$ touch trick.md

1.2. Text Operations

awk cut echo egrep fgrep fmt grep nl sed sort
tr uniq wc

a. awk

awk is the most useful command for handling text files. It operates on an entire file line by line. By default it uses whitespace to separate the fields. The most common syntax for awk command is

1
awk '/search_pattern/ { action_to_take_if_pattern_matches; }' file_to_parse

Lets take following file /etc/passwd. Here’s the sample data that this file contains:

1
2
3
4
5
root:x:0:0:root:/root:/usr/bin/zsh
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync

So now lets get only username from this file. Where -F specifies that on which base we are going to separate the fields. In our case it’s :. { print $1 } means print out the first matching field.

1
awk -F':' '{ print $1 }' /etc/passwd

After running the above command you will get following output.

1
2
3
4
5
root
daemon
bin
sys
sync

For more detail on how to use awk, check following link.

b. cut

Remove sections from each line of files

example.txt

1
red riding hood went to the park to play

show me columns 2 , 7 , and 9 with a space as a separator

1
cut -d " " -f2,7,9 example.txt
1
riding park play

c. echo

Display a line of text

display “Hello World”

1
echo Hello World
1
Hello World

display “Hello World” with newlines between words

1
echo -ne "Hello\nWorld\n"
1
2
Hello
World

d. egrep

Print lines matching a pattern - Extended Expression (alias for: ‘grep -E’)

example.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Lorem ipsum
dolor sit amet,
consetetur
sadipscing elitr,
sed diam nonumy
eirmod tempor
invidunt ut labore
et dolore magna
aliquyam erat, sed
diam voluptua. At
vero eos et
accusam et justo
duo dolores et ea
rebum. Stet clita
kasd gubergren,
no sea takimata
sanctus est Lorem
ipsum dolor sit
amet.

display lines that have either “Lorem” or “dolor” in them.

1
2
3
egrep '(Lorem|dolor)' example.txt
or
grep -E '(Lorem|dolor)' example.txt
1
2
3
4
5
6
Lorem ipsum
dolor sit amet,
et dolore magna
duo dolores et ea
sanctus est Lorem
ipsum dolor sit

e. fgrep

Print lines matching a pattern - FIXED pattern matching (alias for: ‘grep -F’)

example.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Lorem ipsum
dolor sit amet,
consetetur
sadipscing elitr,
sed diam nonumy
eirmod tempor
foo (Lorem|dolor)
invidunt ut labore
et dolore magna
aliquyam erat, sed
diam voluptua. At
vero eos et
accusam et justo
duo dolores et ea
rebum. Stet clita
kasd gubergren,
no sea takimata
sanctus est Lorem
ipsum dolor sit
amet.

Find the exact string ‘(Lorem|dolor)’ in example.txt

1
2
3
fgrep '(Lorem|dolor)' example.txt
or
grep -F '(Lorem|dolor)' example.txt
1
foo (Lorem|dolor)

f. fmt

Simple optimal text formatter

example: example.txt (1 line)

1
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

output the lines of example.txt to 20 character width

1
cat example.txt | fmt -w 20
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Lorem ipsum
dolor sit amet,
consetetur
sadipscing elitr,
sed diam nonumy
eirmod tempor
invidunt ut labore
et dolore magna
aliquyam erat, sed
diam voluptua. At
vero eos et
accusam et justo
duo dolores et ea
rebum. Stet clita
kasd gubergren,
no sea takimata
sanctus est Lorem
ipsum dolor sit
amet.

g. grep

Looks for text inside files. You can use grep to search for lines of text that match one or many regular expressions, and outputs only the matching lines.

1
grep pattern filename

Example:

1
2
3
4
$ grep admin /etc/passwd
_kadmin_admin:*:218:-2:Kerberos Admin Service:/var/empty:/usr/bin/false
_kadmin_changepw:*:219:-2:Kerberos Change Password Service:/var/empty:/usr/bin/false
_krb_kadmin:*:231:-2:Open Directory Kerberos Admin Service:/var/empty:/usr/bin/false

You can also force grep to ignore word case by using -i option. -r can be used to search all files under the specified directory, for example:

1
$ grep -r admin /etc/

And -w to search for words only. For more detail on grep, check following link.

h. nl

Number lines of files

example.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Lorem ipsum
dolor sit amet,
consetetur
sadipscing elitr,
sed diam nonumy
eirmod tempor
invidunt ut labore
et dolore magna
aliquyam erat, sed
diam voluptua. At
vero eos et
accusam et justo
duo dolores et ea
rebum. Stet clita
kasd gubergren,
no sea takimata
sanctus est Lorem
ipsum dolor sit
amet.

show example.txt with line numbers

1
nl -s". " example.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 1. Lorem ipsum
2. dolor sit amet,
3. consetetur
4. sadipscing elitr,
5. sed diam nonumy
6. eirmod tempor
7. invidunt ut labore
8. et dolore magna
9. aliquyam erat, sed
10. diam voluptua. At
11. vero eos et
12. accusam et justo
13. duo dolores et ea
14. rebum. Stet clita
15. kasd gubergren,
16. no sea takimata
17. sanctus est Lorem
18. ipsum dolor sit
19. amet.

i. sed

Stream editor for filtering and transforming text

example.txt

1
Hello This is a Test 1 2 3 4

replace all spaces with hyphens

1
sed 's/ /-/g' example.txt
1
Hello-This-is-a-Test-1-2-3-4

replace all digits with “d”

1
sed 's/[0-9]/d/g' example.txt
1
Hello This is a Test d d d d

j. sort

Sort lines of text files

example.txt

1
2
3
4
5
6
7
f
b
c
g
a
e
d

sort example.txt

1
sort example.txt
1
2
3
4
5
6
7
a
b
c
d
e
f
g

randomize a sorted example.txt

1
sort example.txt | sort -R
1
2
3
4
5
6
7
b
f
a
c
d
g
e

k. tr

Translate or delete characters

example.txt

1
Hello World Foo Bar Baz!

take all lower case letters and make them upper case

1
cat example.txt | tr 'a-z' 'A-Z'
1
HELLO WORLD FOO BAR BAZ!

take all spaces and make them into newlines

1
cat example.txt | tr ' ' '\n'
1
2
3
4
5
Hello
World
Foo
Bar
Baz!

l. uniq

Report or omit repeated lines

example.txt

1
2
3
4
5
6
7
8
a
a
b
a
b
c
d
c

show only unique lines of example.txt (first you need to sort it, otherwise it won’t see the overlap)

1
sort example.txt | uniq
1
2
3
4
a
b
c
d

show the unique items for each line, and tell me how many instances it found

1
sort example.txt | uniq -c
1
2
3
4
3 a
2 b
2 c
1 d

m. wc

Tells you how many lines, words and characters there are in a file.

1
wc filename

Example:

1
2
$ wc demo.txt
7459 15915 398400 demo.txt

Where 7459 is lines, 15915 is words and 398400 is characters.

1.3. Directory Operations

cd mkdir pwd

a. cd

Moves you from one directory to other. Running this

1
$ cd

moves you to home directory. This command accepts an optional dirname, which moves you to that directory.

1
cd dirname

b. mkdir

Makes a new directory.

1
mkdir dirname

You can use this to create multiple directories at once within your current directory.

1
mkdir 1stDirectory 2ndDirectory 3rdDirectory

You can also use this to create parent directories at the same time. For instance, if you wanted a directory named ‘project1’ in another subdirectory at ‘/samples/bash/projects/‘, you could run:

1
mkdir /samples/bash/projects/project1

If any of these directories did no already exist, they would be created as well.

c. pwd

Tells you which directory you currently are in.

1
pwd

1.4. SSH, System Info & Network Operations

bg cal date df dig du fg finger jobs last
man passwd ping ps quota scp ssh top uname uptime
w wget whoami whois

a. bg

Lists stopped or background jobs; resume a stopped job in the background.

b. cal

Shows the month’s calendar.

c. date

Shows the current date and time.

d. df

Shows disk usage.

e. dig

Gets DNS information for domain.

1
dig domain

f. du

Shows the disk usage of files or directories. For more information on this command check this link

1
du [option] [filename|directory]

Options:

  • -h (human readable) Displays output it in kilobytes (K), megabytes (M) and gigabytes (G).
  • -s (supress or summarize) Outputs total disk space of a directory and supresses reports for subdirectories.

Example:

1
2
du -sh pictures
1.4M pictures

g. fg

Brings the most recent job in the foreground.

h. finger

Displays information about user.

1
finger username

i. jobs

Lists the jobs running in the background, giving the job number.

j. last

Lists your last logins of specified user.

1
last yourUsername

k. man

Shows the manual for specified command.

1
man command

l. passwd

Allows the current logged user to change their password.

m. ping

Pings host and outputs results.

1
ping host

n. ps

Lists your processes.

1
ps -u yourusername

Use the flags ef. e for every process and f for full listing.

1
ps -ef

o. quota

Shows what your disk quota is.

1
quota -v

p. scp

Transfer files between a local host and a remote host or between two remote hosts.

copy from local host to remote host

1
scp source_file user@host:directory/target_file

copy from remote host to local host

1
2
scp user@host:directory/source_file target_file
scp -r user@host:directory/source_folder target_folder

This command also accepts an option -P that can be used to connect to specific port.

1
scp -P port user@host:directory/source_file target_file

q. ssh

ssh (SSH client) is a program for logging into and executing commands on a remote machine.

1
ssh user@host

This command also accepts an option -p that can be used to connect to specific port.

1
ssh -p port user@host

r. top

Displays your currently active processes.

s. uname

Shows kernel information.

1
uname -a

t. uptime

Shows current uptime.

u. w

Displays who is online.

v. wget

Downloads file.

1
wget file

w. whoami

Return current logged in username.

x. whois

Gets whois information for domain.

1
whois domain

1.5. Process Monitoring Operations

kill killall & nohup

a. kill

Kills (ends) the processes with the ID you gave.

1
kill PID

b. killall

Kill all processes with the name.

1
killall processname

c. &

The & symbol instructs the command to run as a background process in a subshell.

1
command &

d. nohup

nohup stands for “No Hang Up”. This allows to run command/process or shell script that can continue running in the background after you log out from a shell.

1
nohup command

Combine it with & to create background processes

1
nohup command &

2. Basic Shell Programming

The first line that you will write in bash script files is called shebang. This line in any script determines the script’s ability to be executed like a standalone executable without typing sh, bash, python, php etc beforehand in the terminal.

1
#!/usr/bin/env bash

2.1. Variables

Creating variables in bash is similar to other languages. There are no data types. A variable in bash can contain a number, a character, a string of characters, etc. You have no need to declare a variable, just assigning a value to its reference will create it.

Example:

1
str="hello world"

The above line creates a variable str and assigns “hello world” to it. The value of variable is retrieved by putting the $ in the beginning of variable name.

Example:

1
echo $str   # hello world

2.2. Array

Like other languages bash has also arrays. An array is a variable containing multiple values. There’s no maximum limit on the size of array. Arrays in bash are zero based. The first element is indexed with element 0. There are several ways for creating arrays in bash which are given below.

Examples:

1
2
3
4
5
array[0]=val
array[1]=val
array[2]=val
array=([2]=val [0]=val [1]=val)
array=(val val val)

To display a value at specific index use following syntax:

1
${array[i]}     # where i is the index

If no index is supplied, array element 0 is assumed. To find out how many values there are in the array use the following syntax:

1
${#array[@]}

Bash has also support for the ternary conditions. Check some examples below.

1
2
3
4
${varname:-word}    # if varname exists and isn't null, return its value; otherwise return word
${varname:=word} # if varname exists and isn't null, return its value; otherwise set it word and then return its value
${varname:+word} # if varname exists and isn't null, return word; otherwise return null
${varname:offset:length} # performs substring expansion. It returns the substring of $varname starting at offset and up to length characters

2.3 String Substitution

Check some of the syntax on how to manipulate strings

1
2
3
4
5
6
7
${variable#pattern}         # if the pattern matches the beginning of the variable's value, delete the shortest part that matches and return the rest
${variable##pattern} # if the pattern matches the beginning of the variable's value, delete the longest part that matches and return the rest
${variable%pattern} # if the pattern matches the end of the variable's value, delete the shortest part that matches and return the rest
${variable%%pattern} # if the pattern matches the end of the variable's value, delete the longest part that matches and return the rest
${variable/pattern/string} # the longest match to pattern in variable is replaced by string. Only the first match is replaced
${variable//pattern/string} # the longest match to pattern in variable is replaced by string. All matches are replaced
${#varname} # returns the length of the value of the variable as a character string

2.4. Functions

As in almost any programming language, you can use functions to group pieces of code in a more logical way or practice the divine art of recursion. Declaring a function is just a matter of writing function my_func { my_code }. Calling a function is just like calling another program, you just write its name.

1
2
3
function name() {
shell commands
}

Example:

1
2
3
4
5
6
7
8
9
10
#!/bin/bash
function hello {
echo world!
}
hello

function say {
echo $1
}
say "hello world!"

When you run the above example the hello function will output “world!”. The above two functions hello and say are identical. The main difference is function say. This function, prints the first argument it receives. Arguments, within functions, are treated in the same manner as arguments given to the script.

2.5. Conditionals

The conditional statement in bash is similar to other programming languages. Conditions have many form like the most basic form is if expression then statement where statement is only executed if expression is true.

1
2
3
4
5
if [ expression ]; then
will execute only if expression is true
else
will execute if expression is false
fi

Sometime if conditions becoming confusing so you can write the same condition using the case statements.

1
2
3
4
5
6
7
case expression in
pattern1 )
statements ;;
pattern2 )
statements ;;
...
esac

Expression Examples:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
statement1 && statement2  # both statements are true
statement1 || statement2 # at least one of the statements is true

str1=str2 # str1 matches str2
str1!=str2 # str1 does not match str2
str1<str2 # str1 is less than str2
str1>str2 # str1 is greater than str2
-n str1 # str1 is not null (has length greater than 0)
-z str1 # str1 is null (has length 0)

-a file # file exists
-d file # file exists and is a directory
-e file # file exists; same -a
-f file # file exists and is a regular file (i.e., not a directory or other special type of file)
-r file # you have read permission
-s file # file exists and is not empty
-w file # you have write permission
-x file # you have execute permission on file, or directory search permission if it is a directory
-N file # file was modified since it was last read
-O file # you own file
-G file # file's group ID matches yours (or one of yours, if you are in multiple groups)

file1 -nt file2 # file1 is newer than file2
file1 -ot file2 # file1 is older than file2

-lt # less than
-le # less than or equal
-eq # equal
-ge # greater than or equal
-gt # greater than
-ne # not equal

2.6. Loops

There are three types of loops in bash. for, while and until.

Different for Syntax:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
for x := 1 to 10 do
begin
statements
end

for name [in list]
do
statements that can use $name
done

for (( initialisation ; ending condition ; update ))
do
statements...
done

while Syntax:

1
2
3
while condition; do
statements
done

until Syntax:

1
2
3
until condition; do
statements
done

3. Tricks

Set an alias

Open bash_profile by running following command vim ~/.bash_profile

alias dockerlogin=’ssh www-data@adnan.local -p2222’ # add your alias in .bash_profile

To quickly go to a specific directory

vim ~/.bashrc

export hotellogs=”/workspace/hotel-api/storage/logs”

1
2
source ~/.bashrc
cd $hotellogs

Re-execute the previous command

This goes back to the days before you could rely on keyboards to have an “up” arrow key, but can still be useful.
To run the last command in your history

!!
A common error is to forget to use sudo to prefix a command requiring privileged execution. Instead of typing the whole command again, you can:
sudo !!
This would change a mkdir somedir into sudo mkdir somedir

Exit traps

Make your bash scripts more robust by reliably performing cleanup.

1
2
3
4
5
function finish {
# your cleanup here. e.g. kill any forked processes
jobs -p | xargs kill
}
trap finish EXIT

Saving your environment variables

When you do export FOO = BAR, your variable is only exported in this current shell and all its children, to persist in the future you can simply append in your ~/.bash_profile file the command to export your variable

1
echo export FOO=BAR >> ~/.bash_profile

Accessing your scripts

You can easily access your scripts by creating a bin folder in your home with mkdir ~/bin, now all the scripts you put in this folder you can access in any directory.

If you can not access, try append the code below in your ~/.bash_profile file and after do source ~/.bash_profile.

1
2
3
4
# set PATH so it includes user's private bin if it exists
if [ -d "$HOME/bin" ] ; then
PATH="$HOME/bin:$PATH"
fi

4. Debugging

You can easily debug the bash script by passing different options to bash command. For example -n will not run commands and check for syntax errors only. -v echo commands before running them. -x echo commands after command-line processing.

1
2
3
bash -n scriptname
bash -v scriptname
bash -x scriptname

Customer Obsession

Leaders start with the customer and work backwards. They work vigorously to earn and keep customer trust. Although leaders pay attention to competitors, they obsess over customers.

Ownership

Leaders are owners. They think long term and don’t sacrifice long-term value for short-term results. They act on behalf of the entire company, beyond just their own team. They never say “that’s not my job.”

Invent and Simplify

Leaders expect and require innovation and invention from their teams and always find ways to simplify. They are externally aware, look for new ideas from everywhere, and are not limited by “not invented here.” As we do new things, we accept that we may be misunderstood for long periods of time.

Are Right, A Lot

Leaders are right a lot. They have strong judgment and good instincts. They seek diverse perspectives and work to disconfirm their beliefs.

Learn and Be Curious

Leaders are never done learning and always seek to improve themselves. They are curious about new possibilities and act to explore them.

Hire and Develop the Best

Leaders raise the performance bar with every hire and promotion. They recognize exceptional talent, and willingly move them throughout the organization. Leaders develop leaders and take seriously their role in coaching others. We work on behalf of our people to invent mechanisms for development like Career Choice.

Insist on the Highest Standards

Leaders have relentlessly high standards -; many people may think these standards are unreasonably high. Leaders are continually raising the bar and drive their teams to deliver high quality products, services, and processes. Leaders ensure that defects do not get sent down the line and that problems are fixed so they stay fixed.

Think Big

Thinking small is a self-fulfilling prophecy. Leaders create and communicate a bold direction that inspires results. They think differently and look around corners for ways to serve customers.

Bias for Action

Speed matters in business. Many decisions and actions are reversible and do not need extensive study. We value calculated risk taking.

Frugality

Accomplish more with less. Constraints breed resourcefulness, self-sufficiency, and invention. There are no extra points for growing headcount, budget size, or fixed expense.

Earn Trust

Leaders listen attentively, speak candidly, and treat others respectfully. They are vocally self-critical, even when doing so is awkward or embarrassing. Leaders do not believe their or their team’s body odor smells of perfume. They benchmark themselves and their teams against the best.

Dive Deep

Leaders operate at all levels, stay connected to the details, audit frequently, and are skeptical when metrics and anecdote differ. No task is beneath them.

Have Backbond; Disagree and Commit

Leaders are obligated to respectfully challenge decisions when they disagree, even when doing so is uncomfortable or exhausting. Leaders have conviction and are tenacious. They do not compromise for the sake of social cohesion. Once a decision is determined, they commit wholly.

Deliver Results

Leaders focus on the key inputs for their business and deliver them with the right quality and in a timely fashion. Despite setbacks, they rise to the occasion and never settle.