简体中文 / [English]


Four Years at Nanjing University - Software Engineering and Armchair Programming

 

This article is currently an experimental machine translation and may contain errors. If anything is unclear, please refer to the original Chinese version. I am continuously working to improve the translation.

Conflict of Interest Statement: I am an undergraduate student from the School of Software Engineering at Nanjing University. I have maintained good relationships with faculty, advisors, and classmates during my time here. My academic performance ranked high enough to qualify for graduate recommendation (though I ultimately chose not to pursue it), and I had a smooth and satisfying job-hunting experience. This article is based on personal experiences during my studies and includes subjective opinions. Please read it rationally and objectively. All criticism is directed at the system, not individuals.

I’ve spent four years at NJU, and graduation is just around the corner. Those who know me well are probably aware that I’ve never had much good to say about Nanjing University or its School of Software Engineering.

Originally, I didn’t want to vent much upon graduation. But this blog draft has been sitting in my notes, revised multiple times, for over a year. Abandoning it now would feel like a waste—especially since some of my undergraduate experiences in the software school left me with no choice but to speak up. So I’ll try to calmly record my genuine thoughts and feelings during my time at NJU, purely as a reference for future students.


Let me start by quoting an article that inspired my thinking. I came across it while reviewing for finals last year, and it was the initial spark that made me want to write this piece.

This “culprit” article comes from a blog post by an outstanding alumnus of NJU’s software school. Here is the full original text (original link) and Archive):

Full original text (collapsed due to length)

In practical applications, we need to convert image files into String format for storage. However, I found that the restored file was corrupted after conversion. This article documents the debugging and fixing process.

1. Problematic Code

Without further ado, the following code was my initial attempt, but the resulting image file is corrupted.

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
>public class Example {
private static String readFile(String filename) {
File file = new File(filename);

try {
FileInputStream fis = new FileInputStream(file);
byte[] data = new byte[(int) file.length()];
fis.read(data);
fis.close();
return new String(data);
} catch (IOException e) {
e.printStackTrace();
}
return "Failed to read file!";
}

private static void writeFile(String bin, String filename) {
byte[] data = bin.getBytes();
File file = new File(filename);
if(file.exists()){
file.delete();
}
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file);
fos.write(data,0,data.length);
fos.flush();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}

public static void main(String[] args) {
String originFilename = "./test.png";
String targetFilename = "./result.png";
String fileStr = readFile(originFilename);
writeFile(fileStr, targetFilename);
}
>}

2. Problem Diagnosis

What puzzled me was that according to this logic—reading binary data, converting it to a String, then converting it back—we shouldn’t have encountered a format error.

After repeated thinking and research, I realized that there might be an encoding replacement issue during the conversion between byte[] and String, which corrupted the image.

2.1. String Constructor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>/**
* Constructs a new {@code String} by decoding the specified array of bytes
* using the platform's default charset. The length of the new {@code
* String} is a function of the charset, and hence may not be equal to the
* length of the byte array.
*
* <p> The behavior of this constructor when the given bytes are not valid
* in the default charset is unspecified. The {@link
* java.nio.charset.CharsetDecoder} class should be used when more control
* over the decoding process is required.
*
* @param bytes
* The bytes to be decoded into characters
*
* @since JDK1.1
*/
>public String(byte bytes[]) {
this(bytes, 0, bytes.length);
>}

As seen in the String constructor above, when no encoding is specified, the system default charset is used—in my case (macOS), UTF-8. UTF-8 is a variable-length encoding, using 1, 2, or 3 bytes, so I suspected issues arose during encoding/decoding.

2.2. Decoding Process

1
2
3
4
5
6
7
8
>private StringDecoder(Charset cs, String rcn) {
this.requestedCharsetName = rcn;
this.cs = cs;
this.cd = cs.newDecoder()
.onMalformedInput(CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE);
this.isTrusted = (cs.getClass().getClassLoader0() == null);
>}

The key component StringDecoder used during decoding includes the CodingErrorAction.REPLACE parameter, which means malformed input will be dropped and replaced with a default replacement character.

1
2
3
4
5
6
>/**
* Action indicating that a coding error is to be handled by dropping the
* erroneous input, appending the coder's replacement value to the output
* buffer, and resuming the coding operation.
*/
>public static final CodingErrorAction REPLACE = new CodingErrorAction("REPLACE");

This means invalid byte sequences are replaced. When the byte array conforms to UTF-8, no problem occurs. Otherwise, parts are replaced, leading to a corrupted output file.

3. Solution

Based on the above analysis, we can confirm the issue lies in encoding. We can resolve it using a single-byte encoding (e.g., ISO_8859_1). The corrected code is as follows:

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
>public class Example {
/** Use single-byte encoding **/
private static final Charset charset = StandardCharsets.ISO_8859_1;
private static String readFile(String filename) {
File file = new File(filename);

try {
FileInputStream fis = new FileInputStream(file);
byte[] data = new byte[(int) file.length()];
fis.read(data);
fis.close();
return new String(data, charset);
} catch (IOException e) {
e.printStackTrace();
}
return "Failed to read file!";
}

private static void writeFile(String bin, String filename) {
byte[] data = bin.getBytes(charset);
File file = new File(filename);
if(file.exists()){
file.delete();
}
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file);
fos.write(data,0,data.length);
fos.flush();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}

public static void main(String[] args) {
String originFilename = "./test.png";
String targetFilename = "./result.png";
String fileStr = readFile(originFilename);
writeFile(fileStr, targetFilename);
}
>}

4. Reference Blog

https://www.codeboy.me/2021/03/23/java-string-bytes/


When I first read this article, I couldn’t help but laugh—half in disbelief, half in mild frustration. It’s packed with classic beginner-level mistakes. Please forgive me for using it as a typical negative example.

I’m sure many sharp readers have already spotted the issues. But considering some readers might not be familiar with programming or Java, I’ll point out all the problems I see.

Errors and comments (collapsed due to length)

To begin with, the very idea of converting an image to a String for storage is already baffling. But since the whole article is built on this premise, I’ll address it later.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private static String readFile(String filename) {
File file = new File(filename);

try {
FileInputStream fis = new FileInputStream(file);
byte[] data = new byte[(int) file.length()];
fis.read(data);
fis.close();
return new String(data);
} catch (IOException e) {
e.printStackTrace();
}
return "Failed to read file!";
}

Right off the bat, the Java code is shockingly bad. Writing a file-reading helper function and handling IO exceptions by merely printing the stack trace—then silently swallowing the exception and returning a magic string “Failed to read file!”—is a terrible practice.

When you don’t know how to handle an exception, the best approach is to throw it. Even if you hate Java’s checked exceptions, you can use Lombok’s SneakyThrows. Silently swallowing exceptions (and returning arbitrary values) is a recipe for chaos down the line. At this point, not closing FileInputStream on exception is almost a minor issue.

Also, allocating a byte array of file.length() and calling read once is incorrect. Many read methods in programming languages are thin wrappers around the POSIX read system call, which reads at most n bytes and may require looping until the buffer is full. A much simpler and correct alternative is Files.readAllBytes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private static void writeFile(String bin, String filename) {
byte[] data = bin.getBytes();
File file = new File(filename);
if(file.exists()){
file.delete();
}
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file);
fos.write(data,0,data.length);
fos.flush();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}

In the next function, the author gives up entirely—no return value, no compiler errors, so why not just swallow the exception too?

Also, checking if the file exists and deleting it is redundant. FileOutputStream overwrites the file by default.

Calling flush() on a FileOutputStream is also unnecessary—it has no internal buffer, so flush() does nothing. Even for BufferedOutputStream, close() automatically triggers flush().

What puzzled me was that according to this logic—reading binary data, converting it to a String, then converting it back—we shouldn’t have encountered a format error.

After repeated thinking and research, I realized that there might be an encoding replacement issue during the conversion between byte[] and String, which corrupted the image.

What puzzles me is that he treats String and byte[] as interchangeable and lossless.

Based on the above analysis, we can confirm the issue lies in encoding. We can resolve it using a single-byte encoding (e.g., ISO_8859_1).

I thought he’d eventually realize how absurd this whole approach is and propose a more reasonable solution.

When I saw ISO 8859-1, I gave up all hope. He’s determined to store an image as a String, isn’t he?


This article reflects a beginner-level programming skillset—lacking fundamental computer science knowledge and practical coding skills. Yet the author is a top-ranked graduate from the software school. I’m not criticizing this senior personally—in fact, he seems hardworking, diligent, and generous in sharing. I’m grateful for his course notes, which helped me quickly pass many finals.

What I want to highlight is that this kind of bizarre discrepancy is not rare at NJU. I’m only citing this one particularly illustrative example to give experienced programmers a tangible sense of the issue. In reality, many students—and even graduates—struggle with similar problems:

  • Severe lack of fundamentals and coding experience: e.g., not knowing how to handle Java exceptions, perform file I/O, understand the difference between String and byte[], or grasp character encoding. Not knowing how the read syscall works is almost embarrassing—these are basics. It’s like an English major not knowing what “abandon” means. But from my observation, such students are not few.
  • “Armchair programming” resulting from the above: When you lack even the most basic knowledge, it’s hard to connect concepts and write correct code.
    A classic example is the XY Problem. The entire article smells of XY: Why store an image as a String? If it’s for embedding in web pages or JSON (which don’t support binary), Base64 encoding is the standard solution. If it’s for a database, don’t most support BLOB types? The original problem isn’t explained.
    String was never meant to store binary data. Even in rare edge cases, you’d use hacks like Base64 and explain the trade-offs. Arbitrarily picking a single-byte encoding that maps 0x00–0xFF is not a real solution—it’s a band-aid that barely works by accident.

In any case, I can’t accept this pile of broken or barely-functional code as correct. It’s not what a well-educated computer science student should produce. It’s a clear sign of lacking basic developer literacy. The author’s blog has dozens of Spring Boot tutorials—on reflection, dependency injection, deep dives into Spring source code—yet he stumbles on the most basic Java features. That’s hard to reconcile.

If even our top graduates are this weak in programming skills, then this is symptomatic of Nanjing University’s software school—and indeed many Chinese CS programs:

You look at our curriculum, and everything seems perfectly reasonable: programming, computer organization, networks, data structures, OS, plus software engineering specialties. But in reality, course quality varies wildly, and many courses do more harm than good.

Based on my experience, I divide NJU software courses into three categories:

  • First category: High-quality courses. Examples include Prof. Wei Hengfeng’s C Programming (CPL) and [Compiler Principles], or CS Prof. Jiang Yanyan’s Operating Systems course. Prof. Wei actually codes live in class—something I didn’t appreciate as a freshman, but now realize is rare. Prof. Jiang’s labs are especially fun, updated yearly, and openly available with lecture videos.

    I don’t need to praise them much—these courses naturally have good reputations, even beyond NJU.

  • Second category: Rote, textbook-style lectures—common at many schools and widely criticized—just reading stale, yellowed PPTs.

    For example, the networking course starts with OSI Layer 1 (Physical), then Layer 2 (Data Link), then Layer 3 (Network)… dumping a pile of abstract concepts per lecture with no real-world application. The Linux course is just listing common bash commands and flags.

    I call these courses “if you don’t understand, you won’t; if you do, you don’t need to attend.” If someone actually learns networking from that PPT without prior knowledge, I’d call them a genius.

    Seriously, I’ve seen students taking handwritten notes to memorize Linux commands. The learning outcome is predictable.

  • Third category: Completely outdated, self-invented “in-house” courses. If the software school only had the first two types, I could say it’s neutral.

    But the reality is that there are countless bizarre courses—possibly more than the first two combined. In these courses, students seem to be learning computer science, but are actually just memorizing disconnected, meaningless terms and concepts. Some concepts are half-understood or distorted versions of real CS ideas; others are outright made up. Final exams involve regurgitating definitions or solving “CS-flavored” elementary math word problems—pure busywork, a complete waste of time.

    Let’s look at real past exam questions from NJU software courses, which mirror the course content:

    From the course “Computer Operating Systems”: I call these “CS-flavored” word problems—seemingly related to real applications, but completely disconnected from actual operating systems. In essence, they test memorization of made-up concepts. Explain the concept to a primary schooler, and it becomes a brute-force simulation math problem.

    From “Software Engineering and Computing II”: Teaching diagramming isn’t bad—some skills are occasionally useful. But dedicating an entire semester to requirements analysis and abstract concepts, while most students haven’t even learned Java, and testing mostly on drawing diagrams or rote memorization—doesn’t this miss the point?

    From “Software System Design”: This course’s content is nowhere to be found on the internet. It’s a homemade relic from over a decade ago—strange, arbitrary, and with only one “correct” answer: rote memorization. What does this have to do with modern software development?

    【2025】【2015B】【2016A】【2019】Difference between Architecture, Structure, and Design? What is difference between architecture and design? What is difference between architecture and structure?

    All architectures are software designs, but not all designs are architectures; architecture is part of the design process. Architecture provides an abstract view of design. Architecture is high-level design and a set of design decisions; the software architecture of a program or computing system is one or more structures that include software elements, their externally visible properties, and relationships. Architecture is high-level structure. Architecture defines structure. The properties of a structure are caused by architectural design decisions.

    1. Design contains Architecture, Architecture contains Structure;
    2. Architecture is about software design; all architectures are designs, but not all designs are architectures; architecture is a part of software design;
    3. Structure is static and logical, about how the system is composed;
    4. Architecture includes structure, relationships between components, and defines some dynamic behaviors.

    From “Software Quality Management”: Same deal—useless memorization questions, recycled every year. The entire exam is made of such questions.

    【2018】【2020-mid】Difference and relationship between lifecycle models and software processes

    1. (3’) A lifecycle model is a human-defined division of the software development process.
    2. (3’) A lifecycle model is a framework for software development, a coarse-grained partition.
    3. (2’) Lifecycle models often exclude technical practices.

    If students forget it after the exam, that’s fine. But if they actually memorize this incoherent, even self-contradictory content, it actively harms their understanding of computer science. When they finally write real code, they might “cleverly” recall a concept from class—and most likely go completely off track.

These courses can’t be dismissed as merely outdated or disconnected. Over years of “inheritance,” rigorous knowledge has been blurred and distorted, with no real-world grounding or verification. They’ve become pseudoscience, actively misleading students’ understanding of computer science.

I don’t buy into conspiracy theories about obedience training, but the prevalence of second- and third-category courses—ranging from reading PPTs to fabricating content—means they’re not designed to teach modern, systematic knowledge. They’ve effectively become obedience tests.

In my view, this state of affairs stems from pure “inaction.”

NJU’s century-old history is praised as “rich heritage,” but frankly, it’s “entrenched inertia.” Here, we have the opposite of scientific truth-seeking, innovation, and exploration. Comfortable, stable positions don’t require professors to improve or be accountable for teaching outcomes.

This is a disaster for computer science. Most teachers no longer master what they’re teaching and have no incentive to update. Everyone just pretends everything’s fine.

To maintain appearances amid superficial reforms, some have mastered the art of dressing up outdated or wrong ideas as advanced and correct. Genuine academic passion or responsible teaching has become a rare coincidence of personal drive and luck. Year after year, things naturally and inevitably degrade into what we see today.

This inertia and isolation are strongly intergenerational: As everyone knows, the Gaokao itself is a nationwide overfitting contest for high schoolers. You can’t assume all freshmen have the ability to independently judge course quality or self-learn.

Nanjing University’s software school is a natural trap. Freshmen lacking CS fundamentals, if they continue their high school habits and apply their strong execution to maximize GPA, end up the more they study, the further they drift from real computer science and software development. Knowledge becomes an isolated island. Then, through the “graduate recommendation → PhD → faculty” pipeline, the most overfitted students become dispassionate professors in another decade. Truly, “education completes its loop right here.”


I don’t say this to blame any student or teacher. Acquiring knowledge and teaching it are both extraordinary tasks. The former requires immense time and effort, enduring countless lonely moments in pursuit of fleeting insights, repeated endlessly. The latter requires mastery first, then the skill and patience to pass it on.

I’ve voluntarily served as a TA—revising the Networking Lab Manual, designing experiments for Software Engineering I and Software Engineering II. I’ve invested significant time, but my contribution feels minimal—far from excellent lab design, let alone competing with the many “image projects” professors must maintain in today’s academic system.

The current university system offers almost no space or real incentives for deep, demanding teaching and learning. Don’t expect systemic improvement to happen spontaneously—it’s lucky if things don’t get worse.

To quote Prof. Jiang Yanyan’s high-EQ expression out of context (read the full text if interested):

Of course, no matter how flawed the system is, the nation’s ultimate macro strategy is based on the belief that “rare events become inevitable given enough trials.” As long as the system keeps running, that’s sufficient. Those of us who saw the internet when it was still the Wild West have already “awakened” with an innovative gene—people capable of doing big things. For the country, as long as some soil can grow such people, the rest will find their place.

I’d put it more bluntly: Due to massive historical baggage and lack of resources, NJU software school currently cannot provide undergraduate CS education at even a basic acceptable level. And during my four years, I saw no genuine effort toward improvement. Meanwhile, it’s mass-producing graduates who aren’t even qualified as “industrial products.”


Based on my crude anecdotal observations, many other Chinese CS programs face similar issues. If you’re currently studying in such a program, your best bet is self-preservation. From my personal and biased perspective, here are some suggestions—purely for reference:

  • Don’t hesitate to assume the worst about your courses’ quality
    If you can’t judge a course’s quality independently and lack trustworthy sources confirming it meets basic standards, assume it’s not worth a single minute of your time—just quietly leave.
    If the instructor enforces strict attendance, that’s even more proof the course has become pure formality. Get to class early, grab a back-row seat, put on noise-canceling headphones, do your own thing—or have a classmate sign in for you.

  • Allocate your time and energy wisely
    You’re in college now—GPA isn’t everything. After graduation, no one will care about your grades (they might not even care about your degree or school). You can achieve high ROI on GPA by cramming right before finals using short-term memory and exam tricks (e.g., memorizing PPT content, practicing past papers, reviewing peer notes—varies by school). After the exam, forget it all.
    If you plan to work in tech, in my opinion (and that of some colleagues), a master’s degree is often unnecessary—staying in Chinese academia is just a waste of time. Many grad programs involve taking equally bad courses + unpaid labor in a professor’s side project. (Given the above, you can probably guess the research quality.)
    But if you have other goals—civil service exams, school prestige, or just enjoying campus life—you might need to spend extra time during finals week to boost your GPA above the graduate recommendation threshold, or prepare for the postgraduate entrance exam.

  • Forget high school “learning”
    Most high schools revolve entirely around the Gaokao—a massive overfitting contest filled with terrible study habits and mental programming that hinder real learning. Now that you’re in college, your Gaokao score is useless beyond casual bragging. Leave those bad habits behind.
    Coincidentally, right as I’m writing this, Bilibili UP “Manshi” just released a video that perfectly echoes what I’m saying—so I won’t repeat it. See: BV1wRM2zrEDm

  • Learn to self-learn
    This is a complex topic, but since it’s not directly related, I’ll keep it brief:

    Computer science is open and inclusive. Learning it doesn’t require expensive gear or elite access. You just need a laptop. The only limits are your own ability and imagination.

  • “Metaphysics? Drop out.” Find something you enjoy, and enjoy college
    Everyone has different strengths. If you’re not into CS or put in effort with little progress, that’s perfectly normal. Don’t panic—today’s tech industry doesn’t require every employee to be an expert. Check employment reports: even past NJU grads land big tech jobs at high rates. Don’t waste time on meaningless competition.


At this point of graduation, I’ve been wondering: what does this diploma really mean? It doesn’t prove basic technical skill or scientific literacy, learning or research ability, work experience, or even obedience.

Does a better university degree imply higher professional competence? That’s hard to statistically verify. I haven’t found studies, but I strongly doubt the correlation is significant—or even positive.

Even if there is a strong correlation, I wouldn’t credit today’s higher education for it. Statistics include causal inference—maybe 985/211 students simply had better pre-college education, broader horizons, stronger foundational skills. “Students nationwide learning the same excellent courses together” is just a comforting social illusion. In reality, at NJU software school, many third-year courses with supposed enrollments of over 100 students have only a handful attending. Offering these courses might make no difference compared to giving all students four years of self-study.

Why hasn’t the diploma become worthless? Partly due to widespread “ignorance”: among the general population, most aren’t 985/211 grads. People tend to romanticize what they haven’t experienced. Like classic rags-to-riches dramas, through deliberate or accidental cultivation, degrees have become symbolic “scholar-officials”—vehicles for fantasies of success and wealth, the carrot on the stick. When something becomes detached from reality and turns into faith or superstition, you can’t judge it by logic.

The other reason is real-world degree discrimination: with more graduates than jobs, and limited hiring budgets (HC), companies must screen thousands of resumes. Though individual evaluation based on actual needs would be fairer—and even randomly discarding some resumes would be more equitable—many companies still filter by degree. But usually, degree bias only applies to campus hires, and fast-growing, high-demand teams are more open-minded. It’s a two-way selection.


Another insight I’ve gained: every selective test or interview is a two-way choice. If the selection method is deeply flawed, don’t expect it to magically improve.

Just as countless students, ground down by the Gaokao and stripped of curiosity and intrinsic motivation, don’t suddenly become creative after receiving an acceptance letter, when an institution’s barriers are already so high, oppressive, exclusive, and artificial, its internal culture is more likely to consolidate stagnation and conservatism—focused on maintaining its monopoly on “scarcity,” hostile to real creativity and diversity. It’s not just universities—when a selection process clearly doesn’t fit you, running away early might be the smarter choice.


I think I’ve finally said everything I wanted to. Thanks for reading this far. On the verge of graduation, I wish everyone—no matter what path you choose—may you stay passionate and never lose your ideals.

This article is licensed under the CC BY-NC-SA 4.0 license.

Author: lyc8503, Article link: https://blog.lyc8503.net/en/post/4-years-at-nju/
If this article was helpful or interesting to you, consider buy me a coffee¬_¬
Feel free to comment in English below o/